literal 0.2.1 → 1.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -156
- data/lib/literal/data_structure.rb +24 -15
- data/lib/literal/enum.rb +18 -13
- 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 +202 -32
- data/lib/literal/version.rb +1 -1
- data/lib/literal.rb +8 -3
- metadata +10 -6
- data/lib/literal.test.rb +0 -5
@@ -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(&)
|