literal 1.0.0 → 1.2.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.
@@ -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