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
@@ -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
 
@@ -4,12 +4,13 @@ module Dry
4
4
  module Types
5
5
  class Default
6
6
  include Type
7
- include Dry::Equalizer(:type, :options, :value)
8
7
  include Decorator
9
8
  include Builder
9
+ include Printable
10
+ include Dry::Equalizer(:type, :options, :value, inspect: false)
10
11
 
11
12
  class Callable < Default
12
- include Dry::Equalizer(:type, :options)
13
+ include Dry::Equalizer(:type, :options, inspect: false)
13
14
 
14
15
  # Evaluates given callable
15
16
  # @return [Object]
@@ -67,8 +68,7 @@ module Dry
67
68
  if input.equal?(Undefined)
68
69
  evaluate
69
70
  else
70
- output = type[input]
71
- output.nil? ? evaluate : output
71
+ Undefined.default(type[input]) { evaluate }
72
72
  end
73
73
  end
74
74
  alias_method :[], :call
@@ -4,7 +4,7 @@ module Dry
4
4
  module Types
5
5
  class Enum
6
6
  include Type
7
- include Dry::Equalizer(:type, :options, :mapping)
7
+ include Dry::Equalizer(:type, :options, :mapping, inspect: false)
8
8
  include Decorator
9
9
 
10
10
  # @return [Array]
@@ -53,18 +53,25 @@ module Dry
53
53
 
54
54
  # @api public
55
55
  #
56
- # @see Definition#to_ast
56
+ # @see Nominal#to_ast
57
57
  def to_ast(meta: true)
58
58
  [:enum, [type.to_ast(meta: meta),
59
59
  mapping,
60
60
  meta ? self.meta : EMPTY_HASH]]
61
61
  end
62
62
 
63
+ # @return [String]
64
+ # @api public
65
+ def to_s
66
+ PRINTER.(self)
67
+ end
68
+ alias_method :inspect, :to_s
69
+
63
70
  private
64
71
 
65
72
  # Maps a value
66
73
  #
67
- # @params [Object]
74
+ # @param [Object]
68
75
  # @return [Object]
69
76
  # @api private
70
77
  def map_value(input)
@@ -3,7 +3,7 @@ module Dry
3
3
  extend Dry::Core::ClassAttributes
4
4
 
5
5
  # @!attribute [r] namespace
6
- # @return [Container{String => Definition}]
6
+ # @return [Container{String => Nominal}]
7
7
  defines :namespace
8
8
 
9
9
  namespace self
@@ -5,7 +5,7 @@ module Dry
5
5
  module Types
6
6
  class Maybe
7
7
  include Type
8
- include Dry::Equalizer(:type, :options)
8
+ include Dry::Equalizer(:type, :options, inspect: false)
9
9
  include Decorator
10
10
  include Builder
11
11
  include Dry::Monads::Maybe::Mixin
@@ -60,6 +60,16 @@ module Dry
60
60
  end
61
61
  end
62
62
 
63
+ class Printer
64
+ MAPPING[Maybe] = :visit_maybe
65
+
66
+ def visit_maybe(maybe)
67
+ visit(maybe.type) do |type|
68
+ yield "Maybe<#{ type }>"
69
+ end
70
+ end
71
+ end
72
+
63
73
  # Register non-coercible maybe types
64
74
  NON_NIL.each_key do |name|
65
75
  register("maybe.strict.#{name}", self["strict.#{name}"].maybe)
@@ -1,27 +1,26 @@
1
- require 'dry/types/hash/schema_builder'
1
+ require 'dry/types/hash/constructor'
2
2
 
3
3
  module Dry
4
4
  module Types
5
- class Hash < Definition
6
- SCHEMA_BUILDER = SchemaBuilder.new.freeze
7
-
8
- # @param [{Symbol => Definition}] type_map
9
- # @param [Symbol] constructor
10
- # @return [Schema]
11
- def schema(type_map, constructor = nil)
12
- member_types = transform_types(type_map)
13
-
14
- if constructor.nil?
15
- Schema.new(primitive, member_types: member_types, **options, meta: meta)
5
+ class Hash < Nominal
6
+ NOT_REQUIRED = { required: false }.freeze
7
+
8
+ # @overload schmea(type_map, meta = EMPTY_HASH)
9
+ # @param [{Symbol => Dry::Types::Nominal}] type_map
10
+ # @param [Hash] meta
11
+ # @return [Dry::Types::Schema]
12
+ # @overload schema(keys)
13
+ # @param [Array<Dry::Types::Schema::Key>] key List of schema keys
14
+ # @param [Hash] meta
15
+ # @return [Dry::Types::Schema]
16
+ def schema(keys_or_map, meta = EMPTY_HASH)
17
+ if keys_or_map.is_a?(::Array)
18
+ keys = keys_or_map
16
19
  else
17
- SCHEMA_BUILDER.(
18
- primitive,
19
- **options,
20
- member_types: member_types,
21
- meta: meta,
22
- hash_type: constructor
23
- )
20
+ keys = build_keys(keys_or_map)
24
21
  end
22
+
23
+ Schema.new(primitive, keys: keys, **options, meta: self.meta.merge(meta))
25
24
  end
26
25
 
27
26
  # Build a map type
@@ -38,43 +37,16 @@ module Dry
38
37
  )
39
38
  end
40
39
 
41
- # @param [{Symbol => Definition}] type_map
42
- # @return [Schema]
43
- def weak(type_map)
44
- schema(type_map, :weak)
45
- end
46
-
47
- # @param [{Symbol => Definition}] type_map
48
- # @return [Schema]
49
- def permissive(type_map)
50
- schema(type_map, :permissive)
51
- end
52
-
53
- # @param [{Symbol => Definition}] type_map
40
+ # @param [{Symbol => Nominal}] type_map
54
41
  # @return [Schema]
55
- def strict(type_map)
56
- schema(type_map, :strict)
57
- end
58
-
59
- # @param [{Symbol => Definition}] type_map
60
- # @return [Schema]
61
- def strict_with_defaults(type_map)
62
- schema(type_map, :strict_with_defaults)
63
- end
64
-
65
- # @param [{Symbol => Definition}] type_map
66
- # @return [Schema]
67
- def symbolized(type_map)
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)
42
+ def weak(*)
43
+ raise "Support for old hash schemas was removed, please refer to the CHANGELOG "\
44
+ "on how to proceed with the new API https://github.com/dry-rb/dry-types/blob/master/CHANGELOG.md"
77
45
  end
46
+ alias_method :permissive, :weak
47
+ alias_method :strict, :weak
48
+ alias_method :strict_with_defaults, :weak
49
+ alias_method :symbolized, :weak
78
50
 
79
51
  # Injects a type transformation function for building schemas
80
52
  # @param [#call,nil] proc
@@ -88,22 +60,45 @@ module Dry
88
60
  end
89
61
 
90
62
  handle = Dry::Types::FnContainer.register(fn)
91
- meta(type_transform_fn: handle)
63
+ with(type_transform_fn: handle)
64
+ end
65
+
66
+ # @api private
67
+ def constructor_type
68
+ ::Dry::Types::Hash::Constructor
69
+ end
70
+
71
+ # Whether the type transforms types of schemas created by {Dry::Types::Hash#schema}
72
+ # @return [Boolean]
73
+ # @api public
74
+ def transform_types?
75
+ !options[:type_transform_fn].nil?
76
+ end
77
+
78
+ # @param meta [Boolean] Whether to dump the meta to the AST
79
+ # @return [Array] An AST representation
80
+ def to_ast(meta: true)
81
+ if RUBY_VERSION >= "2.5"
82
+ opts = options.slice(:type_transform_fn)
83
+ else
84
+ opts = options.select { |k, _| k == :type_transform_fn }
85
+ end
86
+
87
+ [:hash, [opts, meta ? self.meta : EMPTY_HASH]]
92
88
  end
93
89
 
94
90
  private
95
91
 
96
92
  # @api private
97
- def transform_types(type_map)
98
- type_fn = meta.fetch(:type_transform_fn, Schema::NO_TRANSFORM)
93
+ def build_keys(type_map)
94
+ type_fn = options.fetch(:type_transform_fn, Schema::NO_TRANSFORM)
99
95
  type_transform = Dry::Types::FnContainer[type_fn]
100
96
 
101
- type_map.each_with_object({}) { |(name, type), result|
102
- result[name] = type_transform.(
103
- resolve_type(type),
104
- name
105
- )
106
- }
97
+ type_map.map do |map_key, type|
98
+ name, options = key_name(map_key)
99
+ key = Schema::Key.new(resolve_type(type), name, options)
100
+ type_transform.(key)
101
+ end
107
102
  end
108
103
 
109
104
  # @api private
@@ -113,6 +108,18 @@ module Dry
113
108
  else type
114
109
  end
115
110
  end
111
+
112
+ # @api private
113
+ def key_name(key)
114
+ if key.to_s.end_with?('?')
115
+ [key.to_s.chop.to_sym, NOT_REQUIRED]
116
+ else
117
+ [key, EMPTY_HASH]
118
+ end
119
+ end
116
120
  end
117
121
  end
118
122
  end
123
+
124
+ require 'dry/types/schema/key'
125
+ require 'dry/types/schema'
@@ -0,0 +1,20 @@
1
+ require 'dry/types/constructor'
2
+
3
+ module Dry
4
+ module Types
5
+ class Hash < Nominal
6
+ class Constructor < ::Dry::Types::Constructor
7
+ # @api private
8
+ def constructor_type
9
+ ::Dry::Types::Hash::Constructor
10
+ end
11
+
12
+ private
13
+
14
+ def composable?(value)
15
+ super && !value.is_a?(Schema::Key)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -3,31 +3,31 @@ require 'dry/types/coercions/json'
3
3
  module Dry
4
4
  module Types
5
5
  register('json.nil') do
6
- self['nil'].constructor(Coercions::JSON.method(:to_nil))
6
+ self['nominal.nil'].constructor(Coercions::JSON.method(:to_nil))
7
7
  end
8
8
 
9
9
  register('json.date') do
10
- self['date'].constructor(Coercions::JSON.method(:to_date))
10
+ self['nominal.date'].constructor(Coercions::JSON.method(:to_date))
11
11
  end
12
12
 
13
13
  register('json.date_time') do
14
- self['date_time'].constructor(Coercions::JSON.method(:to_date_time))
14
+ self['nominal.date_time'].constructor(Coercions::JSON.method(:to_date_time))
15
15
  end
16
16
 
17
17
  register('json.time') do
18
- self['time'].constructor(Coercions::JSON.method(:to_time))
18
+ self['nominal.time'].constructor(Coercions::JSON.method(:to_time))
19
19
  end
20
20
 
21
21
  register('json.decimal') do
22
- self['decimal'].constructor(Coercions::JSON.method(:to_decimal))
22
+ self['nominal.decimal'].constructor(Coercions::JSON.method(:to_decimal))
23
23
  end
24
24
 
25
25
  register('json.array') do
26
- self['array'].safe
26
+ self['nominal.array'].safe
27
27
  end
28
28
 
29
29
  register('json.hash') do
30
- self['hash'].safe
30
+ self['nominal.hash'].safe
31
31
  end
32
32
  end
33
33
  end
data/lib/dry/types/map.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module Dry
2
2
  module Types
3
- class Map < Definition
3
+ class Map < Nominal
4
4
  def initialize(_primitive, key_type: Types['any'], value_type: Types['any'], meta: EMPTY_HASH)
5
5
  super(_primitive, key_type: key_type, value_type: value_type, meta: meta)
6
6
  validate_options!
@@ -53,6 +53,11 @@ module Dry
53
53
  meta ? self.meta : EMPTY_HASH]]
54
54
  end
55
55
 
56
+ # @return [Boolean]
57
+ def constrained?
58
+ value_type.constrained?
59
+ end
60
+
56
61
  private
57
62
 
58
63
  def coerce(input)
@@ -0,0 +1,115 @@
1
+ require 'dry/core/deprecations'
2
+ require 'dry/types/builder_methods'
3
+
4
+ module Dry
5
+ module Types
6
+ # Export types registered in a container as module constants.
7
+ # @example
8
+ # module Types
9
+ # include Dry::Types.module(:strict, :coercible, :nominal, default: :strict)
10
+ # end
11
+ # # Types.constants
12
+ # # => [:Class, :Strict, :Symbol, :Integer, :Float, :String, :Array, :Hash,
13
+ # # :Decimal, :Nil, :True, :False, :Bool, :Date, :Nominal, :DateTime, :Range,
14
+ # # :Coercible, :Time]
15
+ class Module < ::Module
16
+ def initialize(registry, *args)
17
+ @registry = registry
18
+ check_parameters(*args)
19
+ constants = type_constants(*args)
20
+ define_constants(constants)
21
+ extend(BuilderMethods)
22
+
23
+ if constants.key?(:Nominal)
24
+ singleton_class.send(:define_method, :included) do |base|
25
+ super(base)
26
+ base.instance_exec(const_get(:Nominal, false)) do |nominal|
27
+ extend Dry::Core::Deprecations[:'dry-types']
28
+ const_set(:Definition, nominal)
29
+ deprecate_constant(:Definition, message: "Nominal")
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ # @api private
36
+ def type_constants(*namespaces, default: Undefined, **aliases)
37
+ if namespaces.empty? && aliases.empty? && Undefined.equal?(default)
38
+ default_ns = :Strict
39
+ elsif Undefined.equal?(default)
40
+ default_ns = Undefined
41
+ else
42
+ default_ns = Inflector.camelize(default).to_sym
43
+ end
44
+
45
+ tree = registry_tree
46
+
47
+ if namespaces.empty? && aliases.empty?
48
+ modules = tree.select { |_, v| v.is_a?(::Hash) }.map(&:first)
49
+ else
50
+ modules = (namespaces + aliases.keys).map { |n| Inflector.camelize(n).to_sym }
51
+ end
52
+
53
+ tree.each_with_object({}) do |(key, value), constants|
54
+ if modules.include?(key)
55
+ name = aliases.fetch(Inflector.underscore(key).to_sym, key)
56
+ constants[name] = value
57
+ end
58
+
59
+ constants.update(value) if key == default_ns
60
+ end
61
+ end
62
+
63
+ # @api private
64
+ def registry_tree
65
+ @registry_tree ||= @registry.keys.each_with_object({}) { |key, tree|
66
+ type = @registry[key]
67
+ *modules, const_name = key.split('.').map { |part|
68
+ Inflector.camelize(part).to_sym
69
+ }
70
+ next if modules.empty?
71
+
72
+ modules.reduce(tree) { |br, name| br[name] ||= {} }[const_name] = type
73
+ }.freeze
74
+ end
75
+
76
+ private
77
+
78
+ # @api private
79
+ def check_parameters(*namespaces, default: Undefined, **aliases)
80
+ referenced = namespaces.dup
81
+ referenced << default unless false.equal?(default) || Undefined.equal?(default)
82
+ referenced.concat(aliases.keys)
83
+
84
+ known = @registry.keys.map { |k|
85
+ ns, *path = k.split('.')
86
+ ns.to_sym unless path.empty?
87
+ }.compact.uniq
88
+
89
+ (referenced.uniq - known).each do |name|
90
+ raise ArgumentError,
91
+ "#{ name.inspect } is not a known type namespace. "\
92
+ "Supported options are #{ known.map(&:inspect).join(', ') }"
93
+ end
94
+ end
95
+
96
+ # @api private
97
+ def define_constants(constants, mod = self)
98
+ constants.each do |name, value|
99
+ case value
100
+ when ::Hash
101
+ if mod.const_defined?(name, false)
102
+ define_constants(value, mod.const_get(name, false))
103
+ else
104
+ m = ::Module.new
105
+ mod.const_set(name, m)
106
+ define_constants(value, m)
107
+ end
108
+ else
109
+ mod.const_set(name, value)
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end