dry-types 0.15.0 → 1.2.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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +10 -0
  3. data/.github/ISSUE_TEMPLATE/---bug-report.md +34 -0
  4. data/.github/ISSUE_TEMPLATE/---feature-request.md +18 -0
  5. data/.gitignore +1 -0
  6. data/.rubocop.yml +18 -2
  7. data/.travis.yml +10 -5
  8. data/.yardopts +6 -2
  9. data/CHANGELOG.md +186 -3
  10. data/Gemfile +11 -5
  11. data/README.md +4 -3
  12. data/Rakefile +4 -2
  13. data/benchmarks/hash_schemas.rb +10 -6
  14. data/benchmarks/lax_schema.rb +15 -0
  15. data/benchmarks/profile_invalid_input.rb +15 -0
  16. data/benchmarks/profile_lax_schema_valid.rb +16 -0
  17. data/benchmarks/profile_valid_input.rb +15 -0
  18. data/benchmarks/schema_valid_vs_invalid.rb +21 -0
  19. data/benchmarks/setup.rb +17 -0
  20. data/docsite/source/array-with-member.html.md +13 -0
  21. data/docsite/source/built-in-types.html.md +116 -0
  22. data/docsite/source/constraints.html.md +31 -0
  23. data/docsite/source/custom-types.html.md +93 -0
  24. data/docsite/source/default-values.html.md +91 -0
  25. data/docsite/source/enum.html.md +69 -0
  26. data/docsite/source/getting-started.html.md +57 -0
  27. data/docsite/source/hash-schemas.html.md +169 -0
  28. data/docsite/source/index.html.md +155 -0
  29. data/docsite/source/map.html.md +17 -0
  30. data/docsite/source/optional-values.html.md +96 -0
  31. data/docsite/source/sum.html.md +21 -0
  32. data/dry-types.gemspec +21 -19
  33. data/lib/dry-types.rb +2 -0
  34. data/lib/dry/types.rb +60 -17
  35. data/lib/dry/types/any.rb +21 -10
  36. data/lib/dry/types/array.rb +17 -1
  37. data/lib/dry/types/array/constructor.rb +32 -0
  38. data/lib/dry/types/array/member.rb +72 -13
  39. data/lib/dry/types/builder.rb +49 -5
  40. data/lib/dry/types/builder_methods.rb +43 -16
  41. data/lib/dry/types/coercions.rb +84 -19
  42. data/lib/dry/types/coercions/json.rb +22 -3
  43. data/lib/dry/types/coercions/params.rb +98 -30
  44. data/lib/dry/types/compiler.rb +35 -12
  45. data/lib/dry/types/constrained.rb +78 -27
  46. data/lib/dry/types/constrained/coercible.rb +36 -6
  47. data/lib/dry/types/constraints.rb +15 -1
  48. data/lib/dry/types/constructor.rb +77 -62
  49. data/lib/dry/types/constructor/function.rb +200 -0
  50. data/lib/dry/types/container.rb +5 -0
  51. data/lib/dry/types/core.rb +35 -14
  52. data/lib/dry/types/decorator.rb +37 -10
  53. data/lib/dry/types/default.rb +48 -16
  54. data/lib/dry/types/enum.rb +31 -16
  55. data/lib/dry/types/errors.rb +73 -7
  56. data/lib/dry/types/extensions.rb +6 -0
  57. data/lib/dry/types/extensions/maybe.rb +52 -5
  58. data/lib/dry/types/extensions/monads.rb +29 -0
  59. data/lib/dry/types/fn_container.rb +5 -0
  60. data/lib/dry/types/hash.rb +32 -14
  61. data/lib/dry/types/hash/constructor.rb +16 -3
  62. data/lib/dry/types/inflector.rb +2 -0
  63. data/lib/dry/types/json.rb +7 -5
  64. data/lib/dry/types/{safe.rb → lax.rb} +33 -16
  65. data/lib/dry/types/map.rb +70 -32
  66. data/lib/dry/types/meta.rb +51 -0
  67. data/lib/dry/types/module.rb +10 -5
  68. data/lib/dry/types/nominal.rb +105 -14
  69. data/lib/dry/types/options.rb +12 -25
  70. data/lib/dry/types/params.rb +14 -3
  71. data/lib/dry/types/predicate_inferrer.rb +197 -0
  72. data/lib/dry/types/predicate_registry.rb +34 -0
  73. data/lib/dry/types/primitive_inferrer.rb +97 -0
  74. data/lib/dry/types/printable.rb +5 -1
  75. data/lib/dry/types/printer.rb +70 -64
  76. data/lib/dry/types/result.rb +26 -0
  77. data/lib/dry/types/schema.rb +177 -80
  78. data/lib/dry/types/schema/key.rb +48 -35
  79. data/lib/dry/types/spec/types.rb +43 -6
  80. data/lib/dry/types/sum.rb +70 -21
  81. data/lib/dry/types/type.rb +49 -0
  82. data/lib/dry/types/version.rb +3 -1
  83. metadata +91 -62
@@ -1,3 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  Dry::Types.register_extension(:maybe) do
2
4
  require 'dry/types/extensions/maybe'
3
5
  end
6
+
7
+ Dry::Types.register_extension(:monads) do
8
+ require 'dry/types/extensions/monads'
9
+ end
@@ -1,31 +1,58 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'dry/monads/maybe'
2
4
  require 'dry/types/decorator'
3
5
 
4
6
  module Dry
5
7
  module Types
8
+ # Maybe extension provides Maybe types where values are wrapped using `Either` monad
9
+ #
10
+ # @api public
6
11
  class Maybe
7
12
  include Type
8
13
  include Dry::Equalizer(:type, :options, inspect: false)
9
14
  include Decorator
10
15
  include Builder
16
+ include Printable
11
17
  include Dry::Monads::Maybe::Mixin
12
18
 
13
19
  # @param [Dry::Monads::Maybe, Object] input
20
+ #
21
+ # @return [Dry::Monads::Maybe]
22
+ #
23
+ # @api private
24
+ def call_unsafe(input = Undefined)
25
+ case input
26
+ when Dry::Monads::Maybe
27
+ input
28
+ when Undefined
29
+ None()
30
+ else
31
+ Maybe(type.call_unsafe(input))
32
+ end
33
+ end
34
+
35
+ # @param [Dry::Monads::Maybe, Object] input
36
+ #
14
37
  # @return [Dry::Monads::Maybe]
15
- def call(input = Undefined)
38
+ #
39
+ # @api private
40
+ def call_safe(input = Undefined, &block)
16
41
  case input
17
42
  when Dry::Monads::Maybe
18
43
  input
19
44
  when Undefined
20
45
  None()
21
46
  else
22
- Maybe(type[input])
47
+ Maybe(type.call_safe(input, &block))
23
48
  end
24
49
  end
25
- alias_method :[], :call
26
50
 
27
51
  # @param [Object] input
52
+ #
28
53
  # @return [Result::Success]
54
+ #
55
+ # @api public
29
56
  def try(input = Undefined)
30
57
  res = if input.equal?(Undefined)
31
58
  None()
@@ -37,16 +64,22 @@ module Dry
37
64
  end
38
65
 
39
66
  # @return [true]
67
+ #
68
+ # @api public
40
69
  def default?
41
70
  true
42
71
  end
43
72
 
44
73
  # @param [Object] value
74
+ #
45
75
  # @see Dry::Types::Builder#default
76
+ #
46
77
  # @raise [ArgumentError] if nil provided as default value
78
+ #
79
+ # @api public
47
80
  def default(value)
48
81
  if value.nil?
49
- raise ArgumentError, "nil cannot be used as a default of a maybe type"
82
+ raise ArgumentError, 'nil cannot be used as a default of a maybe type'
50
83
  else
51
84
  super
52
85
  end
@@ -54,18 +87,32 @@ module Dry
54
87
  end
55
88
 
56
89
  module Builder
90
+ # Turn a type into a maybe type
91
+ #
57
92
  # @return [Maybe]
93
+ #
94
+ # @api public
58
95
  def maybe
59
96
  Maybe.new(Types['strict.nil'] | self)
60
97
  end
61
98
  end
62
99
 
100
+ # @api private
101
+ class Schema::Key
102
+ # @api private
103
+ def maybe
104
+ __new__(type.maybe)
105
+ end
106
+ end
107
+
108
+ # @api private
63
109
  class Printer
64
110
  MAPPING[Maybe] = :visit_maybe
65
111
 
112
+ # @api private
66
113
  def visit_maybe(maybe)
67
114
  visit(maybe.type) do |type|
68
- yield "Maybe<#{ type }>"
115
+ yield "Maybe<#{type}>"
69
116
  end
70
117
  end
71
118
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/monads/result'
4
+
5
+ module Dry
6
+ module Types
7
+ # Monad extension for Result
8
+ #
9
+ # @api public
10
+ class Result
11
+ include Dry::Monads::Result::Mixin
12
+
13
+ # Turn result into a monad
14
+ #
15
+ # This makes result objects work with dry-monads (or anything with a compatible interface)
16
+ #
17
+ # @return [Dry::Monads::Success,Dry::Monads::Failure]
18
+ #
19
+ # @api public
20
+ def to_monad
21
+ if success?
22
+ Success(input)
23
+ else
24
+ Failure([error, input])
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,7 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'dry/types/container'
2
4
 
3
5
  module Dry
4
6
  module Types
7
+ # Internal container for constructor functions used by the built-in types
8
+ #
9
+ # @api private
5
10
  class FnContainer
6
11
  # @api private
7
12
  def self.container
@@ -1,18 +1,26 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'dry/types/hash/constructor'
2
4
 
3
5
  module Dry
4
6
  module Types
7
+ # Hash types can be used to define maps and schemas
8
+ #
9
+ # @api public
5
10
  class Hash < Nominal
6
11
  NOT_REQUIRED = { required: false }.freeze
7
12
 
8
- # @overload schmea(type_map, meta = EMPTY_HASH)
13
+ # @overload schema(type_map, meta = EMPTY_HASH)
9
14
  # @param [{Symbol => Dry::Types::Nominal}] type_map
10
15
  # @param [Hash] meta
11
16
  # @return [Dry::Types::Schema]
17
+ #
12
18
  # @overload schema(keys)
13
19
  # @param [Array<Dry::Types::Schema::Key>] key List of schema keys
14
20
  # @param [Hash] meta
15
21
  # @return [Dry::Types::Schema]
22
+ #
23
+ # @api public
16
24
  def schema(keys_or_map, meta = EMPTY_HASH)
17
25
  if keys_or_map.is_a?(::Array)
18
26
  keys = keys_or_map
@@ -27,7 +35,10 @@ module Dry
27
35
  #
28
36
  # @param [Type] key_type
29
37
  # @param [Type] value_type
38
+ #
30
39
  # @return [Map]
40
+ #
41
+ # @api public
31
42
  def map(key_type, value_type)
32
43
  Map.new(
33
44
  primitive,
@@ -37,11 +48,10 @@ module Dry
37
48
  )
38
49
  end
39
50
 
40
- # @param [{Symbol => Nominal}] type_map
41
- # @return [Schema]
51
+ # @api private
42
52
  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"
53
+ raise 'Support for old hash schemas was removed, please refer to the CHANGELOG '\
54
+ 'on how to proceed with the new API https://github.com/dry-rb/dry-types/blob/master/CHANGELOG.md'
45
55
  end
46
56
  alias_method :permissive, :weak
47
57
  alias_method :strict, :weak
@@ -49,15 +59,17 @@ module Dry
49
59
  alias_method :symbolized, :weak
50
60
 
51
61
  # Injects a type transformation function for building schemas
62
+ #
52
63
  # @param [#call,nil] proc
53
64
  # @param [#call,nil] block
65
+ #
54
66
  # @return [Hash]
67
+ #
68
+ # @api public
55
69
  def with_type_transform(proc = nil, &block)
56
70
  fn = proc || block
57
71
 
58
- if fn.nil?
59
- raise ArgumentError, "a block or callable argument is required"
60
- end
72
+ raise ArgumentError, 'a block or callable argument is required' if fn.nil?
61
73
 
62
74
  handle = Dry::Types::FnContainer.register(fn)
63
75
  with(type_transform_fn: handle)
@@ -69,20 +81,25 @@ module Dry
69
81
  end
70
82
 
71
83
  # Whether the type transforms types of schemas created by {Dry::Types::Hash#schema}
84
+ #
72
85
  # @return [Boolean]
86
+ #
73
87
  # @api public
74
88
  def transform_types?
75
89
  !options[:type_transform_fn].nil?
76
90
  end
77
91
 
78
92
  # @param meta [Boolean] Whether to dump the meta to the AST
93
+ #
79
94
  # @return [Array] An AST representation
95
+ #
96
+ # @api public
80
97
  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
98
+ opts = if RUBY_VERSION >= '2.5'
99
+ options.slice(:type_transform_fn)
100
+ else
101
+ options.select { |k, _| k == :type_transform_fn }
102
+ end
86
103
 
87
104
  [:hash, [opts, meta ? self.meta : EMPTY_HASH]]
88
105
  end
@@ -104,7 +121,8 @@ module Dry
104
121
  # @api private
105
122
  def resolve_type(type)
106
123
  case type
107
- when String, Class then Types[type]
124
+ when Type then type
125
+ when ::Class, ::String then Types[type]
108
126
  else type
109
127
  end
110
128
  end
@@ -1,7 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'dry/types/constructor'
2
4
 
3
5
  module Dry
4
6
  module Types
7
+ # Hash type exposes additional APIs for working with schema hashes
8
+ #
9
+ # @api public
5
10
  class Hash < Nominal
6
11
  class Constructor < ::Dry::Types::Constructor
7
12
  # @api private
@@ -9,10 +14,18 @@ module Dry
9
14
  ::Dry::Types::Hash::Constructor
10
15
  end
11
16
 
12
- private
17
+ # @return [Lax]
18
+ #
19
+ # @api public
20
+ def lax
21
+ type.lax.constructor(fn, meta: meta)
22
+ end
13
23
 
14
- def composable?(value)
15
- super && !value.is_a?(Schema::Key)
24
+ # @see Dry::Types::Array#of
25
+ #
26
+ # @api public
27
+ def schema(*args)
28
+ type.schema(*args).constructor(fn, meta: meta)
16
29
  end
17
30
  end
18
31
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'dry/inflector'
2
4
 
3
5
  module Dry
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'dry/types/coercions/json'
2
4
 
3
5
  module Dry
@@ -22,12 +24,12 @@ module Dry
22
24
  self['nominal.decimal'].constructor(Coercions::JSON.method(:to_decimal))
23
25
  end
24
26
 
25
- register('json.array') do
26
- self['nominal.array'].safe
27
+ register('json.symbol') do
28
+ self['nominal.symbol'].constructor(Coercions::JSON.method(:to_symbol))
27
29
  end
28
30
 
29
- register('json.hash') do
30
- self['nominal.hash'].safe
31
- end
31
+ register('json.array') { self['array'] }
32
+
33
+ register('json.hash') { self['hash'] }
32
34
  end
33
35
  end
@@ -1,61 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/core/deprecations'
1
4
  require 'dry/types/decorator'
2
5
 
3
6
  module Dry
4
7
  module Types
5
- class Safe
8
+ # Lax types rescue from type-related errors when constructors fail
9
+ #
10
+ # @api public
11
+ class Lax
6
12
  include Type
7
13
  include Decorator
8
14
  include Builder
9
15
  include Printable
10
16
  include Dry::Equalizer(:type, inspect: false)
11
17
 
12
- private :options, :meta
18
+ undef :options, :constructor
13
19
 
14
20
  # @param [Object] input
21
+ #
15
22
  # @return [Object]
23
+ #
24
+ # @api public
16
25
  def call(input)
17
- result = try(input)
18
-
19
- if result.respond_to?(:input)
20
- result.input
21
- else
22
- input
23
- end
26
+ type.call_safe(input) { |output = input| output }
24
27
  end
25
28
  alias_method :[], :call
29
+ alias_method :call_safe, :call
30
+ alias_method :call_unsafe, :call
26
31
 
27
32
  # @param [Object] input
28
33
  # @param [#call,nil] block
34
+ #
29
35
  # @yieldparam [Failure] failure
30
36
  # @yieldreturn [Result]
37
+ #
31
38
  # @return [Result,Logic::Result]
39
+ #
40
+ # @api public
32
41
  def try(input, &block)
33
42
  type.try(input, &block)
34
- rescue TypeError, ArgumentError => e
43
+ rescue CoercionError => e
35
44
  result = failure(input, e.message)
36
45
  block ? yield(result) : result
37
46
  end
38
47
 
39
- # @api public
40
- #
41
48
  # @see Nominal#to_ast
49
+ #
50
+ # @api public
42
51
  def to_ast(meta: true)
43
- [:safe, [type.to_ast(meta: meta), EMPTY_HASH]]
52
+ [:lax, type.to_ast(meta: meta)]
44
53
  end
45
54
 
55
+ # @return [Lax]
56
+ #
46
57
  # @api public
47
- # @return [Safe]
48
- def safe
58
+ def lax
49
59
  self
50
60
  end
51
61
 
52
62
  private
53
63
 
54
64
  # @param [Object, Dry::Types::Constructor] response
65
+ #
55
66
  # @return [Boolean]
67
+ #
68
+ # @api private
56
69
  def decorate?(response)
57
- super || response.kind_of?(Constructor)
70
+ super || response.is_a?(type.constructor_type)
58
71
  end
59
72
  end
73
+
74
+ extend ::Dry::Core::Deprecations[:'dry-types']
75
+ Safe = Lax
76
+ deprecate_constant(:Safe)
60
77
  end
61
78
  end