literal 0.2.0 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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(&)