dry-types 0.14.0 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +15 -0
  3. data/.rubocop.yml +43 -0
  4. data/.travis.yml +2 -3
  5. data/CHANGELOG.md +119 -1
  6. data/Gemfile +2 -2
  7. data/benchmarks/hash_schemas.rb +5 -5
  8. data/dry-types.gemspec +2 -2
  9. data/lib/dry/types.rb +67 -45
  10. data/lib/dry/types/any.rb +11 -2
  11. data/lib/dry/types/array.rb +1 -4
  12. data/lib/dry/types/array/member.rb +2 -2
  13. data/lib/dry/types/builder.rb +23 -3
  14. data/lib/dry/types/builder_methods.rb +10 -11
  15. data/lib/dry/types/coercions/params.rb +2 -0
  16. data/lib/dry/types/compat.rb +0 -2
  17. data/lib/dry/types/compiler.rb +25 -33
  18. data/lib/dry/types/constrained.rb +5 -4
  19. data/lib/dry/types/constructor.rb +33 -12
  20. data/lib/dry/types/container.rb +2 -0
  21. data/lib/dry/types/core.rb +22 -12
  22. data/lib/dry/types/default.rb +4 -4
  23. data/lib/dry/types/enum.rb +10 -3
  24. data/lib/dry/types/errors.rb +1 -1
  25. data/lib/dry/types/extensions/maybe.rb +11 -1
  26. data/lib/dry/types/hash.rb +70 -63
  27. data/lib/dry/types/hash/constructor.rb +20 -0
  28. data/lib/dry/types/json.rb +7 -7
  29. data/lib/dry/types/map.rb +6 -1
  30. data/lib/dry/types/module.rb +115 -0
  31. data/lib/dry/types/{definition.rb → nominal.rb} +10 -4
  32. data/lib/dry/types/options.rb +2 -2
  33. data/lib/dry/types/params.rb +11 -11
  34. data/lib/dry/types/printable.rb +12 -0
  35. data/lib/dry/types/printer.rb +309 -0
  36. data/lib/dry/types/result.rb +2 -2
  37. data/lib/dry/types/safe.rb +4 -2
  38. data/lib/dry/types/schema.rb +298 -0
  39. data/lib/dry/types/schema/key.rb +130 -0
  40. data/lib/dry/types/spec/types.rb +12 -4
  41. data/lib/dry/types/sum.rb +14 -15
  42. data/lib/dry/types/version.rb +1 -1
  43. metadata +22 -12
  44. data/lib/dry/types/compat/form_types.rb +0 -27
  45. data/lib/dry/types/compat/int.rb +0 -14
  46. data/lib/dry/types/hash/schema.rb +0 -199
  47. data/lib/dry/types/hash/schema_builder.rb +0 -75
@@ -1,11 +1,8 @@
1
1
  require 'dry/types/array/member'
2
- require 'dry/core/deprecations'
3
2
 
4
3
  module Dry
5
4
  module Types
6
- class Array < Definition
7
- extend Dry::Core::Deprecations[:'dry-types']
8
-
5
+ class Array < Nominal
9
6
  # @param [Type] type
10
7
  # @return [Array::Member]
11
8
  def of(type)
@@ -1,6 +1,6 @@
1
1
  module Dry
2
2
  module Types
3
- class Array < Definition
3
+ class Array < Nominal
4
4
  class Member < Array
5
5
  # @return [Type]
6
6
  attr_reader :member
@@ -51,7 +51,7 @@ module Dry
51
51
 
52
52
  # @api public
53
53
  #
54
- # @see Definition#to_ast
54
+ # @see Nominal#to_ast
55
55
  def to_ast(meta: true)
56
56
  if member.respond_to?(:to_ast)
57
57
  [:array, [member.to_ast(meta: meta), meta ? self.meta : EMPTY_HASH]]
@@ -1,3 +1,5 @@
1
+ require 'dry/core/deprecations'
2
+
1
3
  module Dry
2
4
  module Types
3
5
  module Builder
@@ -8,6 +10,11 @@ module Dry
8
10
  Constrained
9
11
  end
10
12
 
13
+ # @return [Class]
14
+ def constructor_type
15
+ Constructor
16
+ end
17
+
11
18
  # @param [Type] other
12
19
  # @return [Sum, Sum::Constrained]
13
20
  def |(other)
@@ -27,13 +34,26 @@ module Dry
27
34
  end
28
35
 
29
36
  # @param [Object] input
37
+ # @param [Hash] options
30
38
  # @param [#call,nil] block
31
39
  # @raise [ConstraintError]
32
40
  # @return [Default]
33
- def default(input = Undefined, &block)
41
+ def default(input = Undefined, options = EMPTY_HASH, &block)
42
+ unless input.frozen? || options[:shared]
43
+ where = Dry::Core::Deprecations::STACK.()
44
+ Dry::Core::Deprecations.warn(
45
+ "#{input.inspect} is mutable."\
46
+ ' Be careful: types will return the same instance of the default'\
47
+ ' value every time. Call `.freeze` when setting the default'\
48
+ ' or pass `shared: true` to discard this warning.'\
49
+ "\n#{ where }",
50
+ tag: :'dry-types'
51
+ )
52
+ end
53
+
34
54
  value = input.equal?(Undefined) ? block : input
35
55
 
36
- if value.is_a?(Proc) || valid?(value)
56
+ if value.respond_to?(:call) || valid?(value)
37
57
  Default[value].new(self, value)
38
58
  else
39
59
  raise ConstraintError.new("default value #{value.inspect} violates constraints", value)
@@ -63,7 +83,7 @@ module Dry
63
83
  # @param [#call,nil] block
64
84
  # @return [Constructor]
65
85
  def constructor(constructor = nil, **options, &block)
66
- Constructor.new(with(options), fn: constructor || block)
86
+ constructor_type.new(with(options), fn: constructor || block)
67
87
  end
68
88
  end
69
89
  end
@@ -22,13 +22,12 @@ module Dry
22
22
 
23
23
  # Build a hash schema
24
24
  #
25
- # @param [Symbol] schema Schema type
26
25
  # @param [Hash{Symbol => Dry::Types::Type}] type_map
27
26
  #
28
27
  # @return [Dry::Types::Array]
29
28
  # @api public
30
- def Hash(schema, type_map)
31
- self::Hash.public_send(schema, type_map)
29
+ def Hash(type_map)
30
+ self::Hash.schema(type_map)
32
31
  end
33
32
 
34
33
  # Build a type which values are instances of a given class
@@ -44,7 +43,7 @@ module Dry
44
43
  # @return [Dry::Types::Type]
45
44
  # @api public
46
45
  def Instance(klass)
47
- Definition.new(klass).constrained(type: klass)
46
+ Nominal.new(klass).constrained(type: klass)
48
47
  end
49
48
  alias_method :Strict, :Instance
50
49
 
@@ -56,7 +55,7 @@ module Dry
56
55
  # @return [Dry::Types::Type]
57
56
  # @api public
58
57
  def Value(value)
59
- Definition.new(value.class).constrained(eql: value)
58
+ Nominal.new(value.class).constrained(eql: value)
60
59
  end
61
60
 
62
61
  # Build a type with a single value
@@ -67,7 +66,7 @@ module Dry
67
66
  # @return [Dry::Types::Type]
68
67
  # @api public
69
68
  def Constant(object)
70
- Definition.new(object.class).constrained(is: object)
69
+ Nominal.new(object.class).constrained(is: object)
71
70
  end
72
71
 
73
72
  # Build a constructor type
@@ -80,17 +79,17 @@ module Dry
80
79
  # @return [Dry::Types::Type]
81
80
  # @api public
82
81
  def Constructor(klass, cons = nil, &block)
83
- Definition.new(klass).constructor(cons || block || klass.method(:new))
82
+ Nominal.new(klass).constructor(cons || block || klass.method(:new))
84
83
  end
85
84
 
86
- # Build a definition type
85
+ # Build a nominal type
87
86
  #
88
87
  # @param [Class] klass
89
88
  #
90
89
  # @return [Dry::Types::Type]
91
90
  # @api public
92
- def Definition(klass)
93
- Definition.new(klass)
91
+ def Nominal(klass)
92
+ Nominal.new(klass)
94
93
  end
95
94
 
96
95
  # Build a map type
@@ -105,7 +104,7 @@ module Dry
105
104
  # @return [Dry::Types::Map]
106
105
  # @api public
107
106
  def Map(key_type, value_type)
108
- Types['hash'].map(key_type, value_type)
107
+ Types['nominal.hash'].map(key_type, value_type)
109
108
  end
110
109
  end
111
110
  end
@@ -32,6 +32,8 @@ module Dry
32
32
  def self.to_int(input)
33
33
  if empty_str?(input)
34
34
  nil
35
+ elsif input.is_a? String
36
+ Integer(input, 10)
35
37
  else
36
38
  Integer(input)
37
39
  end
@@ -1,2 +0,0 @@
1
- require 'dry/types/compat/int'
2
- require 'dry/types/compat/form_types'
@@ -17,14 +17,14 @@ module Dry
17
17
  end
18
18
 
19
19
  def visit_constrained(node)
20
- definition, rule, meta = node
21
- Types::Constrained.new(visit(definition), rule: visit_rule(rule)).meta(meta)
20
+ nominal, rule, meta = node
21
+ Types::Constrained.new(visit(nominal), rule: visit_rule(rule)).meta(meta)
22
22
  end
23
23
 
24
24
  def visit_constructor(node)
25
- definition, fn_register_name, meta = node
25
+ nominal, fn_register_name, meta = node
26
26
  fn = Dry::Types::FnContainer[fn_register_name]
27
- primitive = visit(definition)
27
+ primitive = visit(nominal)
28
28
  Types::Constructor.new(primitive, meta: meta, fn: fn)
29
29
  end
30
30
 
@@ -33,13 +33,14 @@ module Dry
33
33
  Types::Safe.new(visit(ast), meta: meta)
34
34
  end
35
35
 
36
- def visit_definition(node)
36
+ def visit_nominal(node)
37
37
  type, meta = node
38
+ nominal_name = "nominal.#{ Types.identifier(type) }"
38
39
 
39
- if registry.registered?(type)
40
- registry[type].meta(meta)
40
+ if registry.registered?(nominal_name)
41
+ registry[nominal_name].meta(meta)
41
42
  else
42
- Definition.new(type, meta: meta)
43
+ Nominal.new(type, meta: meta)
43
44
  end
44
45
  end
45
46
 
@@ -55,22 +56,22 @@ module Dry
55
56
  def visit_array(node)
56
57
  member, meta = node
57
58
  member = member.is_a?(Class) ? member : visit(member)
58
- registry['array'].of(member).meta(meta)
59
+ registry['nominal.array'].of(member).meta(meta)
59
60
  end
60
61
 
61
62
  def visit_hash(node)
62
- constructor, schema, meta = node
63
- merge_with('hash', constructor, schema).meta(meta)
63
+ opts, meta = node
64
+ registry['nominal.hash'].with(opts.merge(meta: meta))
64
65
  end
65
66
 
66
- def visit_hash_schema(node)
67
- schema, meta = node
68
- merge_with_schema('hash', schema).meta(meta)
67
+ def visit_schema(node)
68
+ keys, options, meta = node
69
+ registry['nominal.hash'].schema(keys.map { |key| visit(key) }).with(options.merge(meta: meta))
69
70
  end
70
71
 
71
72
  def visit_json_hash(node)
72
- schema, meta = node
73
- merge_with('json.hash', :symbolized, schema).meta(meta)
73
+ keys, meta = node
74
+ registry['json.hash'].schema(keys.map { |key| visit(key) }, meta)
74
75
  end
75
76
 
76
77
  def visit_json_array(node)
@@ -79,8 +80,8 @@ module Dry
79
80
  end
80
81
 
81
82
  def visit_params_hash(node)
82
- schema, meta = node
83
- merge_with('params.hash', :symbolized, schema).meta(meta)
83
+ keys, meta = node
84
+ registry['params.hash'].schema(keys.map { |key| visit(key) }, meta)
84
85
  end
85
86
 
86
87
  def visit_params_array(node)
@@ -88,9 +89,9 @@ module Dry
88
89
  registry['params.array'].of(visit(member)).meta(meta)
89
90
  end
90
91
 
91
- def visit_member(node)
92
- name, type = node
93
- { name => visit(type) }
92
+ def visit_key(node)
93
+ name, required, type = node
94
+ Schema::Key.new(visit(type), name, required: required)
94
95
  end
95
96
 
96
97
  def visit_enum(node)
@@ -100,20 +101,11 @@ module Dry
100
101
 
101
102
  def visit_map(node)
102
103
  key_type, value_type, meta = node
103
- registry['hash'].map(visit(key_type), visit(value_type)).meta(meta)
104
+ registry['nominal.hash'].map(visit(key_type), visit(value_type)).meta(meta)
104
105
  end
105
106
 
106
- def merge_with(hash_id, constructor, schema)
107
- registry[hash_id].schema(
108
- schema.map { |key| visit(key) }.reduce({}, :update),
109
- constructor
110
- )
111
- end
112
-
113
- def merge_with_schema(hash_id, schema)
114
- registry[hash_id].instantiate(
115
- schema.map { |key| visit(key) }.reduce({}, :update)
116
- )
107
+ def visit_any(meta)
108
+ registry['any'].meta(meta)
117
109
  end
118
110
  end
119
111
  end
@@ -6,9 +6,10 @@ module Dry
6
6
  module Types
7
7
  class Constrained
8
8
  include Type
9
- include Dry::Equalizer(:type, :options, :rule, :meta)
10
9
  include Decorator
11
10
  include Builder
11
+ include Printable
12
+ include Dry::Equalizer(:type, :options, :rule, :meta, inspect: false)
12
13
 
13
14
  # @return [Dry::Logic::Rule]
14
15
  attr_reader :rule
@@ -24,9 +25,9 @@ module Dry
24
25
  # @return [Object]
25
26
  # @raise [ConstraintError]
26
27
  def call(input)
27
- try(input) do |result|
28
+ try(input) { |result|
28
29
  raise ConstraintError.new(result, input)
29
- end.input
30
+ }.input
30
31
  end
31
32
  alias_method :[], :call
32
33
 
@@ -75,7 +76,7 @@ module Dry
75
76
 
76
77
  # @api public
77
78
  #
78
- # @see Definition#to_ast
79
+ # @see Nominal#to_ast
79
80
  def to_ast(meta: true)
80
81
  [:constrained, [type.to_ast(meta: meta),
81
82
  rule.to_ast,
@@ -2,8 +2,8 @@ require 'dry/types/fn_container'
2
2
 
3
3
  module Dry
4
4
  module Types
5
- class Constructor < Definition
6
- include Dry::Equalizer(:type, :options, :meta)
5
+ class Constructor < Nominal
6
+ include Dry::Equalizer(:type, :options, :meta, inspect: false)
7
7
 
8
8
  # @return [#call]
9
9
  attr_reader :fn
@@ -17,7 +17,7 @@ module Dry
17
17
  # @param [Hash] options
18
18
  # @param [#call, nil] block
19
19
  def self.new(input, **options, &block)
20
- type = input.is_a?(Builder) ? input : Definition.new(input)
20
+ type = input.is_a?(Builder) ? input : Nominal.new(input)
21
21
  super(type, **options, &block)
22
22
  end
23
23
 
@@ -43,6 +43,7 @@ module Dry
43
43
  type.name
44
44
  end
45
45
 
46
+ # @return [Boolean]
46
47
  def default?
47
48
  type.default?
48
49
  end
@@ -72,14 +73,16 @@ module Dry
72
73
  left = new_fn || block
73
74
  right = fn
74
75
 
75
- with(options.merge(fn: -> input { left[right[input]] }))
76
+ with(**options, fn: -> input { left[right[input]] })
76
77
  end
78
+ alias_method :append, :constructor
79
+ alias_method :>>, :constructor
77
80
 
78
81
  # @param [Object] value
79
82
  # @return [Boolean]
80
83
  def valid?(value)
81
84
  constructed_value = fn[value]
82
- rescue NoMethodError, TypeError
85
+ rescue NoMethodError, TypeError, ArgumentError
83
86
  false
84
87
  else
85
88
  type.valid?(constructed_value)
@@ -93,13 +96,27 @@ module Dry
93
96
 
94
97
  # @api public
95
98
  #
96
- # @see Definition#to_ast
99
+ # @see Nominal#to_ast
97
100
  def to_ast(meta: true)
98
101
  [:constructor, [type.to_ast(meta: meta),
99
102
  register_fn(fn),
100
103
  meta ? self.meta : EMPTY_HASH]]
101
104
  end
102
105
 
106
+ # @api public
107
+ #
108
+ # @param [#call, nil] new_fn
109
+ # @param [Hash] options
110
+ # @param [#call, nil] block
111
+ # @return [Constructor]
112
+ def prepend(new_fn = nil, **options, &block)
113
+ left = new_fn || block
114
+ right = fn
115
+
116
+ with(**options, fn: -> input { right[left[input]] })
117
+ end
118
+ alias_method :<<, :prepend
119
+
103
120
  private
104
121
 
105
122
  def register_fn(fn)
@@ -114,15 +131,15 @@ module Dry
114
131
  end
115
132
 
116
133
  # Delegates missing methods to {#type}
117
- # @param [Symbol] meth
134
+ # @param [Symbol] method
118
135
  # @param [Array] args
119
136
  # @param [#call, nil] block
120
- def method_missing(meth, *args, &block)
121
- if type.respond_to?(meth)
122
- response = type.__send__(meth, *args, &block)
137
+ def method_missing(method, *args, &block)
138
+ if type.respond_to?(method)
139
+ response = type.__send__(method, *args, &block)
123
140
 
124
- if response.kind_of?(Builder)
125
- self.class.new(response, options)
141
+ if composable?(response)
142
+ response.constructor_type.new(response, options)
126
143
  else
127
144
  response
128
145
  end
@@ -130,6 +147,10 @@ module Dry
130
147
  super
131
148
  end
132
149
  end
150
+
151
+ def composable?(value)
152
+ value.kind_of?(Builder)
153
+ end
133
154
  end
134
155
  end
135
156
  end
@@ -1,3 +1,5 @@
1
+ require 'dry/container'
2
+
1
3
  module Dry
2
4
  module Types
3
5
  class Container
@@ -2,6 +2,7 @@ require 'dry/types/any'
2
2
 
3
3
  module Dry
4
4
  module Types
5
+ # Primitives with {Kernel} coercion methods
5
6
  COERCIBLE = {
6
7
  string: String,
7
8
  integer: Integer,
@@ -11,6 +12,7 @@ module Dry
11
12
  hash: ::Hash
12
13
  }.freeze
13
14
 
15
+ # Primitives that are non-coercible through {Kernel} methods
14
16
  NON_COERCIBLE = {
15
17
  nil: NilClass,
16
18
  symbol: Symbol,
@@ -23,41 +25,49 @@ module Dry
23
25
  range: Range
24
26
  }.freeze
25
27
 
28
+ # All built-in primitives
26
29
  ALL_PRIMITIVES = COERCIBLE.merge(NON_COERCIBLE).freeze
27
30
 
31
+ # All built-in primitives except {NilClass}
28
32
  NON_NIL = ALL_PRIMITIVES.reject { |name, _| name == :nil }.freeze
29
33
 
30
- # Register built-in types that are non-coercible through kernel methods
34
+ # Register generic types for {ALL_PRIMITIVES}
31
35
  ALL_PRIMITIVES.each do |name, primitive|
32
- register(name.to_s, Definition[primitive].new(primitive))
36
+ type = Nominal[primitive].new(primitive)
37
+ register("nominal.#{name}", type)
33
38
  end
34
39
 
35
- # Register strict built-in types that are non-coercible through kernel methods
40
+ # Register strict types for {ALL_PRIMITIVES}
36
41
  ALL_PRIMITIVES.each do |name, primitive|
37
- register("strict.#{name}", self[name.to_s].constrained(type: primitive))
42
+ type = self["nominal.#{name}"].constrained(type: primitive)
43
+ register(name.to_s, type)
44
+ register("strict.#{name}", type)
38
45
  end
39
46
 
40
- # Register built-in primitive types with kernel coercion methods
47
+ # Register {COERCIBLE} types
41
48
  COERCIBLE.each do |name, primitive|
42
- register("coercible.#{name}", self[name.to_s].constructor(Kernel.method(primitive.name)))
49
+ register("coercible.#{name}", self["nominal.#{name}"].constructor(Kernel.method(primitive.name)))
43
50
  end
44
51
 
45
- # Register non-coercible optional types
52
+ # Register optional strict {NON_NIL} types
46
53
  NON_NIL.each_key do |name|
47
54
  register("optional.strict.#{name}", self["strict.#{name}"].optional)
48
55
  end
49
56
 
50
- # Register coercible optional types
57
+ # Register optional {COERCIBLE} types
51
58
  COERCIBLE.each_key do |name|
52
59
  register("optional.coercible.#{name}", self["coercible.#{name}"].optional)
53
60
  end
54
61
 
55
- # Register :bool since it's common and not a built-in Ruby type :(
56
- register("bool", self["true"] | self["false"])
57
- register("strict.bool", self["strict.true"] | self["strict.false"])
62
+ # Register `:bool` since it's common and not a built-in Ruby type :(
63
+ register("nominal.bool", self["nominal.true"] | self["nominal.false"])
64
+ bool = self["strict.true"] | self["strict.false"]
65
+ register("strict.bool", bool)
66
+ register("bool", bool)
58
67
 
59
68
  register("any", Any)
60
- register("object", self['any'])
69
+ register("nominal.any", Any)
70
+ register("strict.any", Any)
61
71
  end
62
72
  end
63
73