dry-types 0.12.3 → 0.13.0

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