dry-types 0.12.3 → 0.13.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,13 @@
1
+ require 'dry/core/deprecations'
2
+
3
+ Dry::Core::Deprecations.warn('Int type was renamed to Integer', tag: :'dry-types')
4
+
5
+ module Dry
6
+ module Types
7
+ register('int', self['integer'])
8
+ register('strict.int', self['strict.integer'])
9
+ register('coercible.int', self['coercible.integer'])
10
+ register('optional.strict.int', self['optional.strict.integer'])
11
+ register('optional.coercible.int', self['optional.coercible.integer'])
12
+ end
13
+ end
@@ -54,7 +54,8 @@ module Dry
54
54
 
55
55
  def visit_array(node)
56
56
  member, meta = node
57
- registry['array'].of(visit(member)).meta(meta)
57
+ member = member.is_a?(Class) ? member : visit(member)
58
+ registry['array'].of(member).meta(meta)
58
59
  end
59
60
 
60
61
  def visit_hash(node)
@@ -62,6 +63,11 @@ module Dry
62
63
  merge_with('hash', constructor, schema).meta(meta)
63
64
  end
64
65
 
66
+ def visit_hash_schema(node)
67
+ schema, meta = node
68
+ merge_with_schema('hash', schema).meta(meta)
69
+ end
70
+
65
71
  def visit_json_hash(node)
66
72
  schema, meta = node
67
73
  merge_with('json.hash', :symbolized, schema).meta(meta)
@@ -72,14 +78,14 @@ module Dry
72
78
  registry['json.array'].of(visit(member)).meta(meta)
73
79
  end
74
80
 
75
- def visit_form_hash(node)
81
+ def visit_params_hash(node)
76
82
  schema, meta = node
77
- merge_with('form.hash', :symbolized, schema).meta(meta)
83
+ merge_with('params.hash', :symbolized, schema).meta(meta)
78
84
  end
79
85
 
80
- def visit_form_array(node)
86
+ def visit_params_array(node)
81
87
  member, meta = node
82
- registry['form.array'].of(visit(member)).meta(meta)
88
+ registry['params.array'].of(visit(member)).meta(meta)
83
89
  end
84
90
 
85
91
  def visit_member(node)
@@ -87,9 +93,26 @@ module Dry
87
93
  { name => visit(type) }
88
94
  end
89
95
 
96
+ def visit_enum(node)
97
+ type, mapping, meta = node
98
+ Enum.new(visit(type), mapping: mapping, meta: meta)
99
+ end
100
+
101
+ def visit_map(node)
102
+ key_type, value_type, meta = node
103
+ registry['hash'].map(visit(key_type), visit(value_type)).meta(meta)
104
+ end
105
+
90
106
  def merge_with(hash_id, constructor, schema)
91
- registry[hash_id].__send__(
92
- constructor, schema.map { |key| visit(key) }.reduce({}, :update)
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)
93
116
  )
94
117
  end
95
118
  end
@@ -1,4 +1,5 @@
1
1
  require 'dry/types/decorator'
2
+ require 'dry/types/fn_container'
2
3
 
3
4
  module Dry
4
5
  module Types
@@ -43,6 +44,10 @@ module Dry
43
44
  type.name
44
45
  end
45
46
 
47
+ def default?
48
+ type.default?
49
+ end
50
+
46
51
  # @param [Object] input
47
52
  # @return [Object]
48
53
  def call(input)
@@ -4,7 +4,7 @@ module Dry
4
4
  module Types
5
5
  COERCIBLE = {
6
6
  string: String,
7
- int: Integer,
7
+ integer: Integer,
8
8
  float: Float,
9
9
  decimal: BigDecimal,
10
10
  array: ::Array,
@@ -19,7 +19,8 @@ module Dry
19
19
  false: FalseClass,
20
20
  date: Date,
21
21
  date_time: DateTime,
22
- time: Time
22
+ time: Time,
23
+ range: Range
23
24
  }.freeze
24
25
 
25
26
  ALL_PRIMITIVES = COERCIBLE.merge(NON_COERCIBLE).freeze
@@ -61,5 +62,5 @@ module Dry
61
62
  end
62
63
 
63
64
  require 'dry/types/coercions'
64
- require 'dry/types/form'
65
+ require 'dry/types/params'
65
66
  require 'dry/types/json'
@@ -39,6 +39,11 @@ module Dry
39
39
  type.constrained?
40
40
  end
41
41
 
42
+ # @return [Sum]
43
+ def optional
44
+ Types['strict.nil'] | self
45
+ end
46
+
42
47
  # @param [Symbol] meth
43
48
  # @param [Boolean] include_private
44
49
  # @return [Boolean]
@@ -59,8 +59,8 @@ module Dry
59
59
 
60
60
  # @param [Object] input
61
61
  # @return [Object] value passed through {#type} or {#default} value
62
- def call(input)
63
- if input.nil?
62
+ def call(input = Undefined)
63
+ if input.equal?(Undefined)
64
64
  evaluate
65
65
  else
66
66
  output = type[input]
@@ -6,12 +6,9 @@ module Dry
6
6
  module Types
7
7
  class Definition
8
8
  include Type
9
- include Dry::Equalizer(:primitive, :options, :meta)
10
9
  include Options
11
10
  include Builder
12
-
13
- # @return [Hash]
14
- attr_reader :options
11
+ include Dry::Equalizer(:primitive, :options, :meta)
15
12
 
16
13
  # @return [Class]
17
14
  attr_reader :primitive
@@ -84,20 +81,12 @@ module Dry
84
81
  Result::Success.new(input)
85
82
  end
86
83
 
87
-
88
84
  # @param (see Failure#initialize)
89
85
  # @return [Result::Failure]
90
86
  def failure(input, error)
91
87
  Result::Failure.new(input, error)
92
88
  end
93
89
 
94
- # @param [Object] klass class of the result instance
95
- # @param [Array] args arguments for the +klass#initialize+ method
96
- # @return [Object] new instance of the given +klass+
97
- def result(klass, *args)
98
- klass.new(*args)
99
- end
100
-
101
90
  # Checks whether value is of a #primitive class
102
91
  # @param [Object] value
103
92
  # @return [Boolean]
@@ -121,3 +110,4 @@ end
121
110
 
122
111
  require 'dry/types/array'
123
112
  require 'dry/types/hash'
113
+ require 'dry/types/map'
@@ -4,7 +4,7 @@ module Dry
4
4
  module Types
5
5
  class Enum
6
6
  include Type
7
- include Dry::Equalizer(:type, :options, :values)
7
+ include Dry::Equalizer(:type, :options, :mapping)
8
8
  include Decorator
9
9
 
10
10
  # @return [Array]
@@ -13,40 +13,68 @@ module Dry
13
13
  # @return [Hash]
14
14
  attr_reader :mapping
15
15
 
16
+ # @return [Hash]
17
+ attr_reader :inverted_mapping
18
+
16
19
  # @param [Type] type
17
20
  # @param [Hash] options
18
21
  # @option options [Array] :values
19
22
  def initialize(type, options)
20
23
  super
21
- @values = options.fetch(:values).freeze
22
- @values.each(&:freeze)
23
- @mapping = values.each_with_object({}) { |v, h| h[values.index(v)] = v }.freeze
24
+ @mapping = options.fetch(:mapping).freeze
25
+ @values = @mapping.keys.freeze
26
+ @inverted_mapping = @mapping.invert.freeze
27
+ freeze
24
28
  end
25
29
 
26
30
  # @param [Object] input
27
31
  # @return [Object]
28
- def call(input)
29
- value =
30
- if values.include?(input)
31
- input
32
- elsif mapping.key?(input)
33
- mapping[input]
34
- end
35
-
36
- type[value || input]
32
+ def call(input = Undefined)
33
+ type[map_value(input)]
37
34
  end
38
35
  alias_method :[], :call
39
36
 
37
+ # @param [Object] input
38
+ # @yieldparam [Failure] failure
39
+ # @yieldreturn [Result]
40
+ # @return [Logic::Result]
41
+ # @return [Object] if coercion fails and a block is given
42
+ def try(input)
43
+ super(map_value(input))
44
+ end
45
+
40
46
  def default(*)
41
47
  raise '.enum(*values).default(value) is not supported. Call '\
42
48
  '.default(value).enum(*values) instead'
43
49
  end
44
50
 
51
+ # Check whether a value is in the enum
52
+ alias_method :include?, :valid?
53
+
45
54
  # @api public
46
55
  #
47
56
  # @see Definition#to_ast
48
57
  def to_ast(meta: true)
49
- [:enum, [type.to_ast(meta: meta), meta ? self.meta : EMPTY_HASH]]
58
+ [:enum, [type.to_ast(meta: meta),
59
+ mapping,
60
+ meta ? self.meta : EMPTY_HASH]]
61
+ end
62
+
63
+ private
64
+
65
+ # Maps a value
66
+ #
67
+ # @params [Object]
68
+ # @return [Object]
69
+ # @api private
70
+ def map_value(input)
71
+ if input.equal?(Undefined)
72
+ type.call
73
+ elsif mapping.key?(input)
74
+ input
75
+ else
76
+ inverted_mapping.fetch(input, input)
77
+ end
50
78
  end
51
79
  end
52
80
  end
@@ -1,10 +1,12 @@
1
1
  module Dry
2
2
  module Types
3
- extend Dry::Configurable
3
+ extend Dry::Core::ClassAttributes
4
4
 
5
5
  # @!attribute [r] namespace
6
6
  # @return [Container{String => Definition}]
7
- setting :namespace, self
7
+ defines :namespace
8
+
9
+ namespace self
8
10
 
9
11
  class SchemaError < TypeError
10
12
  # @param [String,Symbol] key
@@ -15,6 +17,8 @@ module Dry
15
17
  end
16
18
  end
17
19
 
20
+ MapError = Class.new(TypeError)
21
+
18
22
  SchemaKeyError = Class.new(KeyError)
19
23
  private_constant(:SchemaKeyError)
20
24
 
@@ -12,19 +12,32 @@ module Dry
12
12
 
13
13
  # @param [Dry::Monads::Maybe, Object] input
14
14
  # @return [Dry::Monads::Maybe]
15
- def call(input)
16
- input.is_a?(Dry::Monads::Maybe) ? input : Maybe(type[input])
15
+ def call(input = Undefined)
16
+ case input
17
+ when Dry::Monads::Maybe
18
+ input
19
+ when Undefined
20
+ None()
21
+ else
22
+ Maybe(type[input])
23
+ end
17
24
  end
18
25
  alias_method :[], :call
19
26
 
20
27
  # @param [Object] input
21
28
  # @return [Result::Success]
22
- def try(input)
23
- Result::Success.new(Maybe(type[input]))
29
+ def try(input = Undefined)
30
+ res = if input.equal?(Undefined)
31
+ None()
32
+ else
33
+ Maybe(type[input])
34
+ end
35
+
36
+ Result::Success.new(res)
24
37
  end
25
38
 
26
39
  # @return [true]
27
- def maybe?
40
+ def default?
28
41
  true
29
42
  end
30
43
 
@@ -47,29 +60,6 @@ module Dry
47
60
  end
48
61
  end
49
62
 
50
- class Hash
51
- module MaybeTypes
52
- # @param [Hash] result
53
- # @param [Symbol] key
54
- # @param [Definition] type
55
- def resolve_missing_value(result, key, type)
56
- if type.respond_to?(:maybe?) && type.maybe?
57
- result[key] = type[nil]
58
- else
59
- super
60
- end
61
- end
62
- end
63
-
64
- class StrictWithDefaults < Strict
65
- include MaybeTypes
66
- end
67
-
68
- class Schema < Hash
69
- include MaybeTypes
70
- end
71
- end
72
-
73
63
  # Register non-coercible maybe types
74
64
  NON_NIL.each_key do |name|
75
65
  register("maybe.strict.#{name}", self["strict.#{name}"].maybe)
@@ -9,18 +9,19 @@ module Dry
9
9
  end
10
10
 
11
11
  # @api private
12
- def self.register(function)
13
- register_function_name = register_name(function)
14
- container.register(register_function_name, function) unless container.key?(register_function_name)
15
- register_function_name
12
+ def self.register(function = Dry::Core::Constants::Undefined, &block)
13
+ fn = Dry::Core::Constants::Undefined.default(function, block)
14
+ fn_name = register_name(fn)
15
+ container.register(fn_name, fn) unless container.key?(fn_name)
16
+ fn_name
16
17
  end
17
18
 
18
19
  # @api private
19
- def self.[](function_name)
20
- if container.key?(function_name)
21
- container[function_name]
20
+ def self.[](fn_name)
21
+ if container.key?(fn_name)
22
+ container[fn_name]
22
23
  else
23
- function_name
24
+ fn_name
24
25
  end
25
26
  end
26
27
 
@@ -1,62 +1,117 @@
1
- require 'dry/types/hash/schema'
1
+ require 'dry/types/hash/schema_builder'
2
2
 
3
3
  module Dry
4
4
  module Types
5
5
  class Hash < Definition
6
+ SCHEMA_BUILDER = SchemaBuilder.new.freeze
7
+
6
8
  # @param [{Symbol => Definition}] type_map
7
- # @param [Class] klass
8
- # {Schema} or one of its subclasses ({Weak}, {Permissive}, {Strict},
9
- # {StrictWithDefaults}, {Symbolized})
9
+ # @param [Symbol] constructor
10
10
  # @return [Schema]
11
- def schema(type_map, klass = Schema)
12
- member_types = type_map.each_with_object({}) { |(name, type), result|
13
- result[name] =
14
- case type
15
- when String, Class then Types[type]
16
- else type
17
- end
18
- }
11
+ def schema(type_map, constructor = nil)
12
+ member_types = transform_types(type_map)
19
13
 
20
- klass.new(primitive, options.merge(member_types: member_types, meta: meta))
14
+ if constructor.nil?
15
+ Schema.new(primitive, member_types: member_types, **options, meta: meta)
16
+ else
17
+ SCHEMA_BUILDER.(
18
+ primitive,
19
+ **options,
20
+ member_types: member_types,
21
+ meta: meta,
22
+ hash_type: constructor
23
+ )
24
+ end
25
+ end
26
+
27
+ # Build a map type
28
+ #
29
+ # @param [Type] key_type
30
+ # @param [Type] value_type
31
+ # @return [Map]
32
+ def map(key_type, value_type)
33
+ Map.new(
34
+ primitive,
35
+ key_type: resolve_type(key_type),
36
+ value_type: resolve_type(value_type),
37
+ meta: meta
38
+ )
21
39
  end
22
40
 
23
41
  # @param [{Symbol => Definition}] type_map
24
- # @return [Weak]
42
+ # @return [Schema]
25
43
  def weak(type_map)
26
- schema(type_map, Weak)
44
+ schema(type_map, :weak)
27
45
  end
28
46
 
29
47
  # @param [{Symbol => Definition}] type_map
30
- # @return [Permissive]
48
+ # @return [Schema]
31
49
  def permissive(type_map)
32
- schema(type_map, Permissive)
50
+ schema(type_map, :permissive)
33
51
  end
34
52
 
35
53
  # @param [{Symbol => Definition}] type_map
36
- # @return [Strict]
54
+ # @return [Schema]
37
55
  def strict(type_map)
38
- schema(type_map, Strict)
56
+ schema(type_map, :strict)
39
57
  end
40
58
 
41
59
  # @param [{Symbol => Definition}] type_map
42
- # @return [StrictWithDefaults]
60
+ # @return [Schema]
43
61
  def strict_with_defaults(type_map)
44
- schema(type_map, StrictWithDefaults)
62
+ schema(type_map, :strict_with_defaults)
45
63
  end
46
64
 
47
65
  # @param [{Symbol => Definition}] type_map
48
- # @return [Symbolized]
66
+ # @return [Schema]
49
67
  def symbolized(type_map)
50
- schema(type_map, Symbolized)
68
+ schema(type_map, :symbolized)
69
+ end
70
+
71
+ # Build a schema from an AST
72
+ # @api private
73
+ # @param [{Symbol => Definition}] member_types
74
+ # @return [Schema]
75
+ def instantiate(member_types)
76
+ SCHEMA_BUILDER.instantiate(primitive, **options, member_types: member_types)
77
+ end
78
+
79
+ # Injects a type transformation function for building schemas
80
+ # @param [#call,nil] proc
81
+ # @param [#call,nil] block
82
+ # @return [Hash]
83
+ def with_type_transform(proc = nil, &block)
84
+ fn = proc || block
85
+
86
+ if fn.nil?
87
+ raise ArgumentError, "a block or callable argument is required"
88
+ end
89
+
90
+ handle = Dry::Types::FnContainer.register(fn)
91
+ meta(type_transform_fn: handle)
51
92
  end
52
93
 
53
94
  private
54
95
 
55
- # @param [Hash] _result
56
- # @param [Symbol] _key
57
- # @param [Type] _type
58
- def resolve_missing_value(_result, _key, _type)
59
- # noop
96
+ # @api private
97
+ def transform_types(type_map)
98
+ type_fn = meta.fetch(:type_transform_fn, Schema::NO_TRANSFORM)
99
+ type_transform = Dry::Types::FnContainer[type_fn]
100
+
101
+ type_map.each_with_object({}) { |(name, type), result|
102
+ result[name] = type_transform.(
103
+ resolve_type(type),
104
+ name
105
+ )
106
+ }
107
+ end
108
+
109
+ # @api private
110
+ def resolve_type(type)
111
+ case type
112
+ when String, Class then Types[type]
113
+ else type
114
+ end
60
115
  end
61
116
  end
62
117
  end