dry-types 0.14.1 → 0.15.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.
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 +12 -11
  5. data/CHANGELOG.md +115 -4
  6. data/Gemfile +2 -3
  7. data/benchmarks/hash_schemas.rb +5 -5
  8. data/dry-types.gemspec +1 -1
  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 +32 -11
  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 +18 -8
  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
data/lib/dry/types/any.rb CHANGED
@@ -1,6 +1,10 @@
1
1
  module Dry
2
2
  module Types
3
- Any = Class.new(Definition) do
3
+ Any = Class.new(Nominal) do
4
+ def self.name
5
+ 'Any'
6
+ end
7
+
4
8
  def initialize(**options)
5
9
  super(::Object, options)
6
10
  end
@@ -19,9 +23,14 @@ module Dry
19
23
 
20
24
  # @param [Hash] new_options
21
25
  # @return [Type]
22
- def with(new_options)
26
+ def with(**new_options)
23
27
  self.class.new(**options, meta: @meta, **new_options)
24
28
  end
29
+
30
+ # @return [Array]
31
+ def to_ast(meta: true)
32
+ [:any, meta ? self.meta : EMPTY_HASH]
33
+ end
25
34
  end.new
26
35
  end
27
36
  end
@@ -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,8 +73,10 @@ 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]
@@ -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