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.
@@ -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,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "railtie"
4
+ require_relative "rails/patches/active_record"
5
+
6
+ module Literal::Rails
7
+ autoload :EnumType, "literal/rails/enum_type"
8
+ autoload :EnumSerializer, "literal/rails/enum_serializer"
9
+ 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? { |item| @type === item }
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(#{@object_constraints.inspect}, #{@property_constraints.inspect})"
10
+ def inspect = "_Constraint(#{inspect_constraints})"
11
11
 
12
12
  def ===(value)
13
- @object_constraints.all? { |t| t === value } &&
14
- @property_constraints.all? { |a, t| t === value.public_send(a) }
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
@@ -9,6 +9,6 @@ class Literal::Types::EnumerableType
9
9
  def inspect = "_Enumerable(#{@type.inspect})"
10
10
 
11
11
  def ===(value)
12
- Enumerable === value && value.all? { |item| @type === item }
12
+ Enumerable === value && value.all?(@type)
13
13
  end
14
14
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  class Literal::Types::FloatType < Literal::Types::ConstraintType
5
- def inspect = "_Float(#{@range})"
5
+ def inspect = "_Float(#{inspect_constraints})"
6
6
 
7
7
  def ===(value)
8
8
  Float === value && super
@@ -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
- Hash === value && value.all? { |k, v| @key_type === k && @value_type === v }
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
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  class Literal::Types::IntegerType < Literal::Types::ConstraintType
5
- def inspect = "_Integer(#{@range})"
5
+ def inspect = "_Integer(#{inspect_constraints})"
6
6
 
7
7
  def ===(value)
8
8
  Integer === value && super
@@ -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.all? { |k, v| String === k && self === v }
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.all? { |k, t| t === other[k] }
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
- Set === value && value.all? { |item| @type === item }
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
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  class Literal::Types::StringType < Literal::Types::ConstraintType
5
- def inspect = "_String(#{@constraint.inspect})"
5
+ def inspect = "_String(#{inspect_constraints})"
6
6
 
7
7
  def ===(value)
8
8
  String === value && super
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  class Literal::Types::SymbolType < Literal::Types::ConstraintType
5
- def inspect = "_Symbol(#{@constraint.inspect})"
5
+ def inspect = "_Symbol(#{inspect_constraints})"
6
6
 
7
7
  def ===(value)
8
8
  Symbol === value && super
@@ -11,6 +11,16 @@ class Literal::Types::TupleType
11
11
  def inspect = "_Tuple(#{@types.map(&:inspect).join(', ')})"
12
12
 
13
13
  def ===(value)
14
- Array === value && value.size == @types.size && @types.each_with_index.all? { |t, i| t === value[i] }
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 = Set[]
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
- @types.any? { |type| type === value }
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(&)