literal 1.0.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Literal::Hash
4
+ class Generic
5
+ def initialize(key_type, value_type)
6
+ @key_type = key_type
7
+ @value_type = value_type
8
+ end
9
+
10
+ def new(**value)
11
+ Literal::Hash.new(value, key_type: @key_type, value_type: @value_type)
12
+ end
13
+
14
+ def ===(value)
15
+ Literal::Hash === value && @type == value.__type__
16
+ end
17
+
18
+ def inspect
19
+ "Literal::Hash(#{@type.inspect})"
20
+ end
21
+ end
22
+
23
+ include Enumerable
24
+
25
+ def initialize(value, key_type:, value_type:)
26
+ collection_type = Literal::Types::HashType.new(key_type, value_type)
27
+
28
+ Literal.check(actual: value, expected: collection_type) do |c|
29
+ c.fill_receiver(receiver: self, method: "#initialize")
30
+ end
31
+
32
+ @__key_type__ = key_type
33
+ @__value_type__ = value_type
34
+ @__value__ = value
35
+ @__collection_type__ = collection_type
36
+ end
37
+
38
+ attr_reader :__key_type__, :__value_type__, :__value__
39
+
40
+ def freeze
41
+ @__value__.freeze
42
+ super
43
+ end
44
+
45
+ def each(...)
46
+ @__value__.each(...)
47
+ end
48
+ end
@@ -47,8 +47,10 @@ class Literal::Properties::Schema
47
47
  end
48
48
 
49
49
  def generate_initializer(buffer = +"")
50
- buffer << "alias initialize initialize\n"
51
- buffer << "def initialize(#{generate_initializer_params})\n"
50
+ buffer << "alias initialize initialize\n" \
51
+ "def initialize("
52
+ generate_initializer_params(buffer)
53
+ buffer << ")\n"
52
54
  generate_initializer_body(buffer)
53
55
  buffer << "" \
54
56
  "rescue Literal::TypeError => error\n" \
@@ -100,7 +102,7 @@ class Literal::Properties::Schema
100
102
  i, n = 0, sorted_properties.size
101
103
  while i < n
102
104
  property = sorted_properties[i]
103
- buffer << " @" << property.name.name << " == other.#{property.escaped_name}"
105
+ buffer << " @" << property.name.name << " == other." << property.name.name
104
106
  buffer << " &&\n " if i < n - 1
105
107
  i += 1
106
108
  end
@@ -6,8 +6,13 @@ module Literal::Properties
6
6
 
7
7
  include Literal::Types
8
8
 
9
+ module DocString
10
+ # @!method initialize(...)
11
+ end
12
+
9
13
  def self.extended(base)
10
14
  super
15
+ base.include(DocString)
11
16
  base.include(base.__send__(:__literal_extension__))
12
17
  end
13
18
 
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Literal::Rails::FlagsType < ActiveModel::Type::Value
4
+ def initialize(flags_class)
5
+ @flags_class = flags_class
6
+ super()
7
+ end
8
+
9
+ def cast(value)
10
+ case value
11
+ when @flags_class
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 @flags_class
23
+ value.to_bit_string
24
+ else
25
+ raise Literal::ArgumentError.new(
26
+ "Invalid value: #{value.inspect}. Expected an #{@flags_class.inspect}.",
27
+ )
28
+ end
29
+ end
30
+
31
+ def deserialize(value)
32
+ case value
33
+ when nil
34
+ nil
35
+ else
36
+ @flags_class.from_bit_string(value) || raise(
37
+ ArgumentError.new("Invalid value: #{value.inspect} for #{@flags_class}"),
38
+ )
39
+ end
40
+ end
41
+ end
data/lib/literal/rails.rb CHANGED
@@ -5,5 +5,6 @@ require_relative "rails/patches/active_record"
5
5
 
6
6
  module Literal::Rails
7
7
  autoload :EnumType, "literal/rails/enum_type"
8
+ autoload :FlagsType, "literal/rails/flags_type"
8
9
  autoload :EnumSerializer, "literal/rails/enum_serializer"
9
10
  end
@@ -6,8 +6,16 @@ class Literal::Railtie < Rails::Railtie
6
6
  Literal::Rails::EnumType.new(type)
7
7
  end
8
8
 
9
+ ActiveRecord::Type.register(:literal_flags) do |name, type:|
10
+ Literal::Rails::FlagsType.new(type)
11
+ end
12
+
9
13
  ActiveModel::Type.register(:literal_enum) do |name, type:|
10
14
  Literal::Rails::EnumType.new(type)
11
15
  end
16
+
17
+ ActiveModel::Type.register(:literal_flags) do |name, type:|
18
+ Literal::Rails::FlagsType.new(type)
19
+ end
12
20
  end
13
21
  end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Literal::Set
4
+ class Generic
5
+ def initialize(type)
6
+ @type = type
7
+ end
8
+
9
+ def new(*value)
10
+ Literal::Set.new(value.to_set, type: @type)
11
+ end
12
+
13
+ alias_method :[], :new
14
+
15
+ def ===(value)
16
+ Literal::Set === value && @type == value.__type__
17
+ end
18
+
19
+ def inspect
20
+ "Literal::Set(#{@type.inspect})"
21
+ end
22
+ end
23
+
24
+ include Enumerable
25
+
26
+ def initialize(value, type:)
27
+ collection_type = Literal::Types::SetType.new(type)
28
+
29
+ Literal.check(actual: value, expected: collection_type) do |c|
30
+ c.fill_receiver(receiver: self, method: "#initialize")
31
+ end
32
+
33
+ @__type__ = type
34
+ @__value__ = value
35
+ @__collection_type__ = collection_type
36
+ end
37
+
38
+ attr_reader :__type__, :__value__
39
+
40
+ def freeze
41
+ @__value__.freeze
42
+ super
43
+ end
44
+
45
+ def each(...)
46
+ @__value__.each(...)
47
+ end
48
+ end
@@ -6,4 +6,30 @@ class Literal::Struct < Literal::DataStructure
6
6
  super
7
7
  end
8
8
  end
9
+
10
+ def [](key)
11
+ case key
12
+ when Symbol
13
+ when String
14
+ key = key.intern
15
+ else
16
+ raise TypeError.new("expected a string or symbol, got #{key.inspect.class}")
17
+ end
18
+
19
+ prop = self.class.literal_properties[key] || raise(NameError.new("unknown attribute: #{key.inspect} for #{self.class}"))
20
+ __send__(prop.name)
21
+ end
22
+
23
+ def []=(key, value)
24
+ case key
25
+ when Symbol
26
+ when String
27
+ key = key.intern
28
+ else
29
+ raise TypeError.new("expected a string or symbol, got #{key.inspect.class}")
30
+ end
31
+
32
+ prop = self.class.literal_properties[key] || raise(NameError.new("unknown attribute: #{key.inspect} for #{self.class}"))
33
+ __send__(:"#{prop.name}=", value)
34
+ end
9
35
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Literal::Type
4
+ def >=(other)
5
+ self == other
6
+ end
7
+ end
@@ -1,12 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # @api private
4
- module Literal::Types::AnyType
5
- def self.inspect = "_Any"
4
+ class Literal::Types::AnyType
5
+ include Literal::Type
6
6
 
7
- def self.===(value)
7
+ def inspect
8
+ "_Any"
9
+ end
10
+
11
+ def ===(value)
8
12
  !(nil === value)
9
13
  end
10
14
 
15
+ def >=(other)
16
+ !(other === nil)
17
+ end
18
+
19
+ Instance = new.freeze
20
+
11
21
  freeze
12
22
  end
@@ -2,16 +2,31 @@
2
2
 
3
3
  # @api private
4
4
  class Literal::Types::ArrayType
5
+ include Literal::Type
6
+
5
7
  def initialize(type)
6
8
  @type = type
7
9
  end
8
10
 
9
- def inspect = "_Array(#{@type.inspect})"
11
+ attr_reader :type
12
+
13
+ def inspect
14
+ "_Array(#{@type.inspect})"
15
+ end
10
16
 
11
17
  def ===(value)
12
18
  Array === value && value.all?(@type)
13
19
  end
14
20
 
21
+ def >=(other)
22
+ case other
23
+ when Literal::Types::ArrayType
24
+ @type >= other.type
25
+ else
26
+ false
27
+ end
28
+ end
29
+
15
30
  def record_literal_type_errors(context)
16
31
  unless Array === context.actual
17
32
  return
@@ -23,4 +38,6 @@ class Literal::Types::ArrayType
23
38
  end
24
39
  end
25
40
  end
41
+
42
+ freeze
26
43
  end
@@ -1,12 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # @api private
4
- module Literal::Types::BooleanType
5
- def self.inspect = "_Boolean"
4
+ class Literal::Types::BooleanType
5
+ include Literal::Type
6
6
 
7
- def self.===(value)
7
+ def inspect
8
+ "_Boolean"
9
+ end
10
+
11
+ def ===(value)
8
12
  true == value || false == value
9
13
  end
10
14
 
15
+ def >=(other)
16
+ case other
17
+ when true, false, Literal::Types::BooleanType
18
+ true
19
+ else
20
+ false
21
+ end
22
+ end
23
+
24
+ Instance = new.freeze
25
+
11
26
  freeze
12
27
  end
@@ -1,12 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # @api private
4
- module Literal::Types::CallableType
5
- def self.inspect = "_Callable"
4
+ class Literal::Types::CallableType
5
+ include Literal::Type
6
6
 
7
- def self.===(value)
7
+ def inspect
8
+ "_Callable"
9
+ end
10
+
11
+ def ===(value)
8
12
  value.respond_to?(:call)
9
13
  end
10
14
 
15
+ def >=(other)
16
+ (self == other) || (Proc == other) || (Method == other) || case other
17
+ when Literal::Types::IntersectionType
18
+ other.types.any? { |type| Literal.subtype?(type, of: self) }
19
+ when Literal::Types::ConstraintType
20
+ other.object_constraints.any? { |type| Literal.subtype?(type, of: self) }
21
+ when Literal::Types::InterfaceType
22
+ other.methods.include?(:call)
23
+ else
24
+ false
25
+ end
26
+ end
27
+
28
+ Instance = new.freeze
29
+
11
30
  freeze
12
31
  end
@@ -2,13 +2,32 @@
2
2
 
3
3
  # @api private
4
4
  class Literal::Types::ClassType
5
+ include Literal::Type
6
+
5
7
  def initialize(type)
6
8
  @type = type
7
9
  end
8
10
 
9
- def inspect = "_Class(#{@type.name})"
11
+ attr_reader :type
12
+
13
+ def inspect
14
+ "_Class(#{@type.name})"
15
+ end
10
16
 
11
17
  def ===(value)
12
18
  Class === value && (value == @type || value < @type)
13
19
  end
20
+
21
+ def >=(other)
22
+ case other
23
+ when Literal::Types::ClassType
24
+ Literal.subtype?(other.type, of: @type)
25
+ when Literal::Types::DescendantType
26
+ (Class === other.type) && Literal.subtype?(other.type, of: @type)
27
+ else
28
+ false
29
+ end
30
+ end
31
+
32
+ freeze
14
33
  end
@@ -2,12 +2,19 @@
2
2
 
3
3
  # @api private
4
4
  class Literal::Types::ConstraintType
5
+ include Literal::Type
6
+
5
7
  def initialize(*object_constraints, **property_constraints)
6
8
  @object_constraints = object_constraints
7
9
  @property_constraints = property_constraints
8
10
  end
9
11
 
10
- def inspect = "_Constraint(#{inspect_constraints})"
12
+ attr_reader :object_constraints
13
+ attr_reader :property_constraints
14
+
15
+ def inspect
16
+ "_Constraint(#{inspect_constraints})"
17
+ end
11
18
 
12
19
  def ===(value)
13
20
  object_constraints = @object_constraints
@@ -20,11 +27,56 @@ class Literal::Types::ConstraintType
20
27
 
21
28
  @property_constraints.each do |a, t|
22
29
  return false unless t === value.public_send(a)
30
+ rescue NoMethodError => e
31
+ raise unless e.name == a && e.receiver == value
32
+ return false
23
33
  end
24
34
 
25
35
  true
26
36
  end
27
37
 
38
+ def >=(other)
39
+ case other
40
+ when Literal::Types::ConstraintType
41
+ other_object_constraints = other.object_constraints
42
+ return false unless @object_constraints.all? do |constraint|
43
+ other_object_constraints.all? { |c| Literal.subtype?(c, of: constraint) }
44
+ end
45
+
46
+ other_property_constraints = other.property_constraints
47
+ return false unless @property_constraints.all? do |k, v|
48
+ Literal.subtype?(other_property_constraints[k], of: v)
49
+ end
50
+
51
+ true
52
+ when Literal::Types::IntersectionType
53
+ other_object_constraints = other.types
54
+ return false unless @object_constraints.all? do |constraint|
55
+ other_object_constraints.all? { |c| Literal.subtype?(c, of: constraint) }
56
+ end
57
+
58
+ true
59
+ else
60
+ false
61
+ end
62
+ end
63
+
64
+ def record_literal_type_errors(context)
65
+ @object_constraints.each do |constraint|
66
+ next if constraint === context.actual
67
+
68
+ context.add_child(label: inspect, expected: constraint, actual: context.actual)
69
+ end
70
+
71
+ @property_constraints.each do |property, constraint|
72
+ next unless context.actual.respond_to?(property)
73
+ actual = context.actual.public_send(property)
74
+ next if constraint === actual
75
+
76
+ context.add_child(label: ".#{property}", expected: constraint, actual:)
77
+ end
78
+ end
79
+
28
80
  private
29
81
 
30
82
  def inspect_constraints
@@ -42,4 +94,6 @@ class Literal::Types::ConstraintType
42
94
  @property_constraints.map { |k, t| "#{k}: #{t.inspect}" }.join(", ")
43
95
  end
44
96
  end
97
+
98
+ freeze
45
99
  end
@@ -1,13 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Literal::Types::DescendantType
4
+ include Literal::Type
5
+
4
6
  def initialize(type)
5
7
  @type = type
6
8
  end
7
9
 
8
- def inspect = "_Descendant(#{@type})"
10
+ attr_reader :type
11
+
12
+ def inspect
13
+ "_Descendant(#{@type})"
14
+ end
9
15
 
10
16
  def ===(value)
11
17
  Module === value && value < @type
12
18
  end
19
+
20
+ def >=(other)
21
+ case other
22
+ when Literal::Types::DescendantType, Literal::Types::ClassType
23
+ Literal.subtype?(other.type, of: @type)
24
+ else
25
+ false
26
+ end
27
+ end
28
+
29
+ freeze
13
30
  end
@@ -2,13 +2,30 @@
2
2
 
3
3
  # @api private
4
4
  class Literal::Types::EnumerableType
5
+ include Literal::Type
6
+
5
7
  def initialize(type)
6
8
  @type = type
7
9
  end
8
10
 
9
- def inspect = "_Enumerable(#{@type.inspect})"
11
+ attr_reader :type
12
+
13
+ def inspect
14
+ "_Enumerable(#{@type.inspect})"
15
+ end
10
16
 
11
17
  def ===(value)
12
18
  Enumerable === value && value.all?(@type)
13
19
  end
20
+
21
+ def >=(other)
22
+ case other
23
+ when Literal::Types::EnumerableType
24
+ Literal.subtype?(other.type, of: @type)
25
+ else
26
+ false
27
+ end
28
+ end
29
+
30
+ freeze
14
31
  end
@@ -1,12 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # @api private
4
- module Literal::Types::FalsyType
5
- def self.inspect = "_Falsy"
4
+ class Literal::Types::FalsyType
5
+ include Literal::Type
6
6
 
7
- def self.===(value)
7
+ def initialize
8
+ freeze
9
+ end
10
+
11
+ def inspect
12
+ "_Falsy"
13
+ end
14
+
15
+ def ===(value)
8
16
  !value
9
17
  end
10
18
 
19
+ def >=(other)
20
+ case other
21
+ when Literal::Types::FalsyType, nil, false
22
+ true
23
+ else
24
+ false
25
+ end
26
+ end
27
+
28
+ Instance = new.freeze
29
+
11
30
  freeze
12
31
  end
@@ -18,4 +18,21 @@ class Literal::Types::HashType
18
18
 
19
19
  true
20
20
  end
21
+
22
+ def record_literal_type_errors(context)
23
+ unless Hash === context.actual
24
+ return
25
+ end
26
+
27
+ context.actual.each do |key, item|
28
+ unless @key_type === key
29
+ context.add_child(label: "[]", expected: @key_type, actual: key)
30
+ next
31
+ end
32
+
33
+ unless @value_type === item
34
+ context.add_child(label: "[#{key.inspect}]", expected: @value_type, actual: item)
35
+ end
36
+ end
37
+ end
21
38
  end
@@ -7,6 +7,8 @@ class Literal::Types::InterfaceType
7
7
  @methods = methods
8
8
  end
9
9
 
10
+ attr_reader :methods
11
+
10
12
  def inspect = "_Interface(#{@methods.map(&:inspect).join(', ')})"
11
13
 
12
14
  def ===(value)
@@ -8,6 +8,8 @@ class Literal::Types::IntersectionType
8
8
  @types = types
9
9
  end
10
10
 
11
+ attr_reader :types
12
+
11
13
  def inspect = "_Intersection(#{@types.map(&:inspect).join(', ')})"
12
14
 
13
15
  def ===(value)
@@ -17,4 +19,12 @@ class Literal::Types::IntersectionType
17
19
  def nil?
18
20
  @types.all?(&:nil?)
19
21
  end
22
+
23
+ def record_literal_type_errors(context)
24
+ @types.each do |type|
25
+ next if type === context.actual
26
+
27
+ context.add_child(label: inspect, expected: type, actual: context.actual)
28
+ end
29
+ end
20
30
  end
@@ -19,5 +19,21 @@ module Literal::Types::JSONDataType
19
19
  end
20
20
  end
21
21
 
22
+ def self.record_literal_type_errors(context)
23
+ case value = context.actual
24
+ when String, Integer, Float, true, false, nil
25
+ # nothing to do
26
+ when Hash
27
+ value.each do |k, v|
28
+ context.add_child(label: "[]", expected: String, actual: k) unless String === k
29
+ context.add_child(label: "[#{k.inspect}]", expected: self, actual: v) unless self === v
30
+ end
31
+ when Array
32
+ value.each_with_index do |item, index|
33
+ context.add_child(label: "[#{index}]", expected: self, actual: item) unless self === item
34
+ end
35
+ end
36
+ end
37
+
22
38
  freeze
23
39
  end
@@ -21,9 +21,15 @@ class Literal::Types::MapType
21
21
  return
22
22
  end
23
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)
24
+ @shape.each do |key, expected|
25
+ unless context.actual.key?(key) || expected === nil
26
+ context.add_child(label: "[]", expected: key, actual: nil)
27
+ next
28
+ end
29
+
30
+ actual = context.actual[key]
31
+ unless expected === actual
32
+ context.add_child(label: "[#{key.inspect}]", expected:, actual:)
27
33
  end
28
34
  end
29
35
  end