literal 0.2.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +2 -157
- data/lib/literal/data_structure.rb +24 -15
- data/lib/literal/enum.rb +28 -14
- data/lib/literal/errors/type_error.rb +76 -3
- data/lib/literal/properties/data_schema.rb +3 -4
- data/lib/literal/properties/schema.rb +57 -7
- data/lib/literal/properties.rb +18 -4
- data/lib/literal/property.rb +47 -20
- data/lib/literal/rails/enum_serializer.rb +20 -0
- data/lib/literal/rails/enum_type.rb +41 -0
- data/lib/literal/rails/patches/active_record.rb +31 -0
- data/lib/literal/rails.rb +9 -0
- data/lib/literal/railtie.rb +13 -0
- data/lib/literal/types/array_type.rb +13 -1
- data/lib/literal/types/boolean_type.rb +0 -6
- data/lib/literal/types/constraint_type.rb +32 -3
- data/lib/literal/types/enumerable_type.rb +1 -1
- data/lib/literal/types/float_type.rb +1 -1
- data/lib/literal/types/hash_type.rb +7 -1
- data/lib/literal/types/integer_type.rb +1 -1
- data/lib/literal/types/json_data_type.rb +3 -1
- data/lib/literal/types/map_type.rb +15 -1
- data/lib/literal/types/set_type.rb +7 -1
- data/lib/literal/types/string_type.rb +1 -1
- data/lib/literal/types/symbol_type.rb +1 -1
- data/lib/literal/types/tuple_type.rb +11 -1
- data/lib/literal/types/union_type.rb +8 -2
- data/lib/literal/types.rb +190 -35
- data/lib/literal/version.rb +1 -1
- data/lib/literal.rb +8 -3
- metadata +12 -8
- data/lib/literal.test.rb +0 -5
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Literal::Rails::EnumSerializer < ActiveJob::Serializers::ObjectSerializer
|
4
|
+
def serialize?(object)
|
5
|
+
Literal::Enum === object
|
6
|
+
end
|
7
|
+
|
8
|
+
def serialize(object)
|
9
|
+
super([
|
10
|
+
0,
|
11
|
+
object.class.name,
|
12
|
+
object.value,
|
13
|
+
])
|
14
|
+
end
|
15
|
+
|
16
|
+
def deserialize(payload)
|
17
|
+
_version, class_name, value = payload
|
18
|
+
class_name.constantize[value]
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Literal::Rails::EnumType < ActiveModel::Type::Value
|
4
|
+
def initialize(enum)
|
5
|
+
@enum = enum
|
6
|
+
super()
|
7
|
+
end
|
8
|
+
|
9
|
+
def cast(value)
|
10
|
+
case value
|
11
|
+
when @enum
|
12
|
+
value
|
13
|
+
else
|
14
|
+
deserialize(value)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def serialize(value)
|
19
|
+
case value
|
20
|
+
when nil
|
21
|
+
nil
|
22
|
+
when @enum
|
23
|
+
value.value
|
24
|
+
else
|
25
|
+
raise Literal::ArgumentError.new(
|
26
|
+
"Invalid value: #{value.inspect}. Expected an #{@enum.inspect}.",
|
27
|
+
)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def deserialize(value)
|
32
|
+
case value
|
33
|
+
when nil
|
34
|
+
nil
|
35
|
+
else
|
36
|
+
@enum[value] || raise(
|
37
|
+
ArgumentError.new("Invalid value: #{value.inspect} for #{@enum}"),
|
38
|
+
)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
class RelationType
|
5
|
+
def initialize(model_class)
|
6
|
+
unless Class === model_class && model_class < ActiveRecord::Base
|
7
|
+
raise Literal::TypeError.expected(
|
8
|
+
model_class,
|
9
|
+
to_be_a: ActiveRecord::Base,
|
10
|
+
)
|
11
|
+
end
|
12
|
+
|
13
|
+
@model_class = model_class
|
14
|
+
end
|
15
|
+
|
16
|
+
def inspect = "ActiveRecord::Relation(#{@model_class.name})"
|
17
|
+
|
18
|
+
def ===(value)
|
19
|
+
case value
|
20
|
+
when ActiveRecord::Relation, ActiveRecord::Associations::CollectionProxy, ActiveRecord::AssociationRelation
|
21
|
+
@model_class == value.model || value.model < @model_class
|
22
|
+
else
|
23
|
+
false
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.Relation(model)
|
29
|
+
RelationType.new(model)
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Literal::Railtie < Rails::Railtie
|
4
|
+
initializer "literal.register_literal_enum_type" do
|
5
|
+
ActiveRecord::Type.register(:literal_enum) do |name, type:|
|
6
|
+
Literal::Rails::EnumType.new(type)
|
7
|
+
end
|
8
|
+
|
9
|
+
ActiveModel::Type.register(:literal_enum) do |name, type:|
|
10
|
+
Literal::Rails::EnumType.new(type)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -9,6 +9,18 @@ class Literal::Types::ArrayType
|
|
9
9
|
def inspect = "_Array(#{@type.inspect})"
|
10
10
|
|
11
11
|
def ===(value)
|
12
|
-
Array === value && value.all?
|
12
|
+
Array === value && value.all?(@type)
|
13
|
+
end
|
14
|
+
|
15
|
+
def record_literal_type_errors(context)
|
16
|
+
unless Array === context.actual
|
17
|
+
return
|
18
|
+
end
|
19
|
+
|
20
|
+
context.actual.each_with_index do |item, index|
|
21
|
+
unless @type === item
|
22
|
+
context.add_child(label: "[#{index}]", expected: @type, actual: item)
|
23
|
+
end
|
24
|
+
end
|
13
25
|
end
|
14
26
|
end
|
@@ -2,17 +2,11 @@
|
|
2
2
|
|
3
3
|
# @api private
|
4
4
|
module Literal::Types::BooleanType
|
5
|
-
COERCION = proc { |value| !!value }
|
6
|
-
|
7
5
|
def self.inspect = "_Boolean"
|
8
6
|
|
9
7
|
def self.===(value)
|
10
8
|
true == value || false == value
|
11
9
|
end
|
12
10
|
|
13
|
-
def to_proc
|
14
|
-
COERCION
|
15
|
-
end
|
16
|
-
|
17
11
|
freeze
|
18
12
|
end
|
@@ -7,10 +7,39 @@ class Literal::Types::ConstraintType
|
|
7
7
|
@property_constraints = property_constraints
|
8
8
|
end
|
9
9
|
|
10
|
-
def inspect = "_Constraint(#{
|
10
|
+
def inspect = "_Constraint(#{inspect_constraints})"
|
11
11
|
|
12
12
|
def ===(value)
|
13
|
-
|
14
|
-
|
13
|
+
object_constraints = @object_constraints
|
14
|
+
|
15
|
+
i, len = 0, object_constraints.size
|
16
|
+
while i < len
|
17
|
+
return false unless object_constraints[i] === value
|
18
|
+
i += 1
|
19
|
+
end
|
20
|
+
|
21
|
+
@property_constraints.each do |a, t|
|
22
|
+
return false unless t === value.public_send(a)
|
23
|
+
end
|
24
|
+
|
25
|
+
true
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def inspect_constraints
|
31
|
+
[inspect_object_constraints, inspect_property_constraints].compact.join(", ")
|
32
|
+
end
|
33
|
+
|
34
|
+
def inspect_object_constraints
|
35
|
+
if @object_constraints.length > 0
|
36
|
+
@object_constraints.map(&:inspect).join(", ")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def inspect_property_constraints
|
41
|
+
if @property_constraints.length > 0
|
42
|
+
@property_constraints.map { |k, t| "#{k}: #{t.inspect}" }.join(", ")
|
43
|
+
end
|
15
44
|
end
|
16
45
|
end
|
@@ -10,6 +10,12 @@ class Literal::Types::HashType
|
|
10
10
|
def inspect = "_Hash(#{@key_type.inspect}, #{@value_type.inspect})"
|
11
11
|
|
12
12
|
def ===(value)
|
13
|
-
|
13
|
+
return false unless Hash === value
|
14
|
+
|
15
|
+
value.each do |k, v|
|
16
|
+
return false unless @key_type === k && @value_type === v
|
17
|
+
end
|
18
|
+
|
19
|
+
true
|
14
20
|
end
|
15
21
|
end
|
@@ -9,7 +9,9 @@ module Literal::Types::JSONDataType
|
|
9
9
|
when String, Integer, Float, true, false, nil
|
10
10
|
true
|
11
11
|
when Hash
|
12
|
-
value.
|
12
|
+
value.each do |k, v|
|
13
|
+
return false unless String === k && self === v
|
14
|
+
end
|
13
15
|
when Array
|
14
16
|
value.all?(self)
|
15
17
|
else
|
@@ -11,6 +11,20 @@ class Literal::Types::MapType
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def ===(other)
|
14
|
-
@shape.
|
14
|
+
Hash === other && @shape.each do |k, v|
|
15
|
+
return false unless v === other[k]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def record_literal_type_errors(context)
|
20
|
+
unless Hash === context.actual
|
21
|
+
return
|
22
|
+
end
|
23
|
+
|
24
|
+
context.actual.each do |key, item|
|
25
|
+
unless @shape[key] === item
|
26
|
+
context.add_child(label: "[#{key.inspect}]", expected: @shape[key], actual: item)
|
27
|
+
end
|
28
|
+
end
|
15
29
|
end
|
16
30
|
end
|
@@ -9,6 +9,12 @@ class Literal::Types::SetType
|
|
9
9
|
def inspect = "_Set(#{@type.inspect})"
|
10
10
|
|
11
11
|
def ===(value)
|
12
|
-
|
12
|
+
return false unless Set === value
|
13
|
+
|
14
|
+
value.each do |v|
|
15
|
+
return false unless @type === v
|
16
|
+
end
|
17
|
+
|
18
|
+
true
|
13
19
|
end
|
14
20
|
end
|
@@ -11,6 +11,16 @@ class Literal::Types::TupleType
|
|
11
11
|
def inspect = "_Tuple(#{@types.map(&:inspect).join(', ')})"
|
12
12
|
|
13
13
|
def ===(value)
|
14
|
-
|
14
|
+
return false unless Array === value
|
15
|
+
types = @types
|
16
|
+
return false unless value.size == types.size
|
17
|
+
|
18
|
+
i, len = 0, types.size
|
19
|
+
while i < len
|
20
|
+
return false unless types[i] === value[i]
|
21
|
+
i += 1
|
22
|
+
end
|
23
|
+
|
24
|
+
true
|
15
25
|
end
|
16
26
|
end
|
@@ -6,15 +6,21 @@ class Literal::Types::UnionType
|
|
6
6
|
def initialize(*types)
|
7
7
|
raise Literal::ArgumentError.new("_Union type must have at least one type.") if types.size < 1
|
8
8
|
|
9
|
-
@types =
|
9
|
+
@types = []
|
10
10
|
load_types(types)
|
11
|
+
@types.uniq!
|
11
12
|
@types.freeze
|
12
13
|
end
|
13
14
|
|
14
15
|
def inspect = "_Union(#{@types.inspect})"
|
15
16
|
|
16
17
|
def ===(value)
|
17
|
-
|
18
|
+
types = @types
|
19
|
+
i, len = 0, types.size
|
20
|
+
while i < len
|
21
|
+
return true if types[i] === value
|
22
|
+
i += 1
|
23
|
+
end
|
18
24
|
end
|
19
25
|
|
20
26
|
def each(&)
|