dry-types 0.15.0 → 1.2.0

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