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,10 +1,21 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'dry/types/array/member'
4
+ require 'dry/types/array/constructor'
2
5
 
3
6
  module Dry
4
7
  module Types
8
+ # Array type can be used to define an array with optional member type
9
+ #
10
+ # @api public
5
11
  class Array < Nominal
6
- # @param [Type] type
12
+ # Build an array type with a member type
13
+ #
14
+ # @param [Type,#call] type
15
+ #
7
16
  # @return [Array::Member]
17
+ #
18
+ # @api public
8
19
  def of(type)
9
20
  member =
10
21
  case type
@@ -14,6 +25,11 @@ module Dry
14
25
 
15
26
  Array::Member.new(primitive, **options, member: member)
16
27
  end
28
+
29
+ # @api private
30
+ def constructor_type
31
+ ::Dry::Types::Array::Constructor
32
+ end
17
33
  end
18
34
  end
19
35
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/types/constructor'
4
+
5
+ module Dry
6
+ module Types
7
+ # @api public
8
+ class Array < Nominal
9
+ # @api private
10
+ class Constructor < ::Dry::Types::Constructor
11
+ # @api private
12
+ def constructor_type
13
+ ::Dry::Types::Array::Constructor
14
+ end
15
+
16
+ # @return [Lax]
17
+ #
18
+ # @api public
19
+ def lax
20
+ Lax.new(type.lax.constructor(fn, meta: meta))
21
+ end
22
+
23
+ # @see Dry::Types::Array#of
24
+ #
25
+ # @api public
26
+ def of(member)
27
+ type.of(member).constructor(fn, meta: meta)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,46 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/types/array/constructor'
4
+
1
5
  module Dry
2
6
  module Types
3
7
  class Array < Nominal
8
+ # Member arrays define their member type that is applied to each element
9
+ #
10
+ # @api public
4
11
  class Member < Array
5
12
  # @return [Type]
6
13
  attr_reader :member
7
14
 
8
15
  # @param [Class] primitive
9
16
  # @param [Hash] options
17
+ #
10
18
  # @option options [Type] :member
19
+ #
20
+ # @api private
11
21
  def initialize(primitive, options = {})
12
22
  @member = options.fetch(:member)
13
23
  super
14
24
  end
15
25
 
16
26
  # @param [Object] input
17
- # @param [Symbol] meth
27
+ #
18
28
  # @return [Array]
19
- def call(input, meth = :call)
20
- input.map { |el| member.__send__(meth, el) }
29
+ #
30
+ # @api private
31
+ def call_unsafe(input)
32
+ if primitive?(input)
33
+ input.each_with_object([]) do |el, output|
34
+ coerced = member.call_unsafe(el)
35
+
36
+ output << coerced unless Undefined.equal?(coerced)
37
+ end
38
+ else
39
+ super
40
+ end
21
41
  end
22
- alias_method :[], :call
23
42
 
24
- # @param [Array, #all?, Object] value
25
- # @return [Boolean]
26
- def valid?(value)
27
- super && value.all? { |el| member.valid?(el) }
43
+ # @param [Object] input
44
+ # @return [Array]
45
+ #
46
+ # @api private
47
+ def call_safe(input)
48
+ if primitive?(input)
49
+ failed = false
50
+
51
+ result = input.each_with_object([]) do |el, output|
52
+ coerced = member.call_safe(el) { |out = el|
53
+ failed = true
54
+ out
55
+ }
56
+
57
+ output << coerced unless Undefined.equal?(coerced)
58
+ end
59
+
60
+ failed ? yield(result) : result
61
+ else
62
+ yield
63
+ end
28
64
  end
29
65
 
30
66
  # @param [Array, Object] input
31
67
  # @param [#call,nil] block
68
+ #
32
69
  # @yieldparam [Failure] failure
33
70
  # @yieldreturn [Result]
71
+ #
34
72
  # @return [Result,Logic::Result]
73
+ #
74
+ # @api public
35
75
  def try(input, &block)
36
- if input.is_a?(::Array)
37
- result = call(input, :try).reject { |r| r.input.equal?(Undefined) }
38
- output = result.map(&:input)
76
+ if primitive?(input)
77
+ output = []
78
+
79
+ result = input.map { |el| member.try(el) }
80
+ result.each do |r|
81
+ output << r.input unless Undefined.equal?(r.input)
82
+ end
39
83
 
40
84
  if result.all?(&:success?)
41
85
  success(output)
42
86
  else
43
- failure = failure(output, result.select(&:failure?))
87
+ error = result.find(&:failure?).error
88
+ failure = failure(output, error)
44
89
  block ? yield(failure) : failure
45
90
  end
46
91
  else
@@ -49,9 +94,18 @@ module Dry
49
94
  end
50
95
  end
51
96
 
52
- # @api public
97
+ # Build a lax type
53
98
  #
99
+ # @return [Lax]
100
+ #
101
+ # @api public
102
+ def lax
103
+ Lax.new(Member.new(primitive, { **options, member: member.lax, meta: meta }))
104
+ end
105
+
54
106
  # @see Nominal#to_ast
107
+ #
108
+ # @api public
55
109
  def to_ast(meta: true)
56
110
  if member.respond_to?(:to_ast)
57
111
  [:array, [member.to_ast(meta: meta), meta ? self.meta : EMPTY_HASH]]
@@ -59,6 +113,11 @@ module Dry
59
113
  [:array, [member, meta ? self.meta : EMPTY_HASH]]
60
114
  end
61
115
  end
116
+
117
+ # @api private
118
+ def constructor_type
119
+ ::Dry::Types::Array::Constructor
120
+ end
62
121
  end
63
122
  end
64
123
  end
@@ -1,43 +1,72 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'dry/core/deprecations'
2
4
 
3
5
  module Dry
4
6
  module Types
7
+ # Common API for building types and composition
8
+ #
9
+ # @api public
5
10
  module Builder
6
11
  include Dry::Core::Constants
7
12
 
8
13
  # @return [Class]
14
+ #
15
+ # @api private
9
16
  def constrained_type
10
17
  Constrained
11
18
  end
12
19
 
13
20
  # @return [Class]
21
+ #
22
+ # @api private
14
23
  def constructor_type
15
24
  Constructor
16
25
  end
17
26
 
27
+ # Compose two types into a Sum type
28
+ #
18
29
  # @param [Type] other
30
+ #
19
31
  # @return [Sum, Sum::Constrained]
32
+ #
33
+ # @api private
20
34
  def |(other)
21
35
  klass = constrained? && other.constrained? ? Sum::Constrained : Sum
22
36
  klass.new(self, other)
23
37
  end
24
38
 
39
+ # Turn a type into an optional type
40
+ #
25
41
  # @return [Sum]
42
+ #
43
+ # @api public
26
44
  def optional
27
45
  Types['strict.nil'] | self
28
46
  end
29
47
 
48
+ # Turn a type into a constrained type
49
+ #
30
50
  # @param [Hash] options constraining rule (see {Types.Rule})
51
+ #
31
52
  # @return [Constrained]
53
+ #
54
+ # @api public
32
55
  def constrained(options)
33
56
  constrained_type.new(self, rule: Types.Rule(options))
34
57
  end
35
58
 
59
+ # Turn a type into a type with a default value
60
+ #
36
61
  # @param [Object] input
37
62
  # @param [Hash] options
38
63
  # @param [#call,nil] block
64
+ #
39
65
  # @raise [ConstraintError]
66
+ #
40
67
  # @return [Default]
68
+ #
69
+ # @api public
41
70
  def default(input = Undefined, options = EMPTY_HASH, &block)
42
71
  unless input.frozen? || options[:shared]
43
72
  where = Dry::Core::Deprecations::STACK.()
@@ -46,7 +75,7 @@ module Dry
46
75
  ' Be careful: types will return the same instance of the default'\
47
76
  ' value every time. Call `.freeze` when setting the default'\
48
77
  ' or pass `shared: true` to discard this warning.'\
49
- "\n#{ where }",
78
+ "\n#{where}",
50
79
  tag: :'dry-types'
51
80
  )
52
81
  end
@@ -60,8 +89,13 @@ module Dry
60
89
  end
61
90
  end
62
91
 
92
+ # Define an enum on top of the existing type
93
+ #
63
94
  # @param [Array] values
95
+ #
64
96
  # @return [Enum]
97
+ #
98
+ # @api public
65
99
  def enum(*values)
66
100
  mapping =
67
101
  if values.length == 1 && values[0].is_a?(::Hash)
@@ -73,15 +107,25 @@ module Dry
73
107
  Enum.new(constrained(included_in: mapping.keys), mapping: mapping)
74
108
  end
75
109
 
76
- # @return [Safe]
77
- def safe
78
- Safe.new(self)
110
+ # Turn a type into a lax type that will rescue from type-errors and
111
+ # return the original input
112
+ #
113
+ # @return [Lax]
114
+ #
115
+ # @api public
116
+ def lax
117
+ Lax.new(self)
79
118
  end
80
119
 
120
+ # Define a constructor for the type
121
+ #
81
122
  # @param [#call,nil] constructor
82
123
  # @param [Hash] options
83
124
  # @param [#call,nil] block
125
+ #
84
126
  # @return [Constructor]
127
+ #
128
+ # @api public
85
129
  def constructor(constructor = nil, **options, &block)
86
130
  constructor_type.new(with(options), fn: constructor || block)
87
131
  end
@@ -92,5 +136,5 @@ end
92
136
  require 'dry/types/default'
93
137
  require 'dry/types/constrained'
94
138
  require 'dry/types/enum'
95
- require 'dry/types/safe'
139
+ require 'dry/types/lax'
96
140
  require 'dry/types/sum'
@@ -1,5 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Dry
2
4
  module Types
5
+ # Common API for building type objects in a convenient way
6
+ #
7
+ # rubocop:disable Naming/MethodName
8
+ #
9
+ # @api public
3
10
  module BuilderMethods
4
11
  # @api private
5
12
  def included(base)
@@ -8,7 +15,8 @@ module Dry
8
15
  end
9
16
 
10
17
  # Build an array type.
11
- # It is a shortcut for Array.of
18
+ #
19
+ # Shortcut for Array#of.
12
20
  #
13
21
  # @example
14
22
  # Types::Strings = Types.Array(Types::String)
@@ -17,7 +25,7 @@ module Dry
17
25
  #
18
26
  # @return [Dry::Types::Array]
19
27
  def Array(type)
20
- self::Array.of(type)
28
+ Strict(::Array).of(type)
21
29
  end
22
30
 
23
31
  # Build a hash schema
@@ -25,9 +33,8 @@ module Dry
25
33
  # @param [Hash{Symbol => Dry::Types::Type}] type_map
26
34
  #
27
35
  # @return [Dry::Types::Array]
28
- # @api public
29
36
  def Hash(type_map)
30
- self::Hash.schema(type_map)
37
+ Strict(::Hash).schema(type_map)
31
38
  end
32
39
 
33
40
  # Build a type which values are instances of a given class
@@ -41,9 +48,8 @@ module Dry
41
48
  # @param [Class,Module] klass Class or module
42
49
  #
43
50
  # @return [Dry::Types::Type]
44
- # @api public
45
51
  def Instance(klass)
46
- Nominal.new(klass).constrained(type: klass)
52
+ Nominal(klass).constrained(type: klass)
47
53
  end
48
54
  alias_method :Strict, :Instance
49
55
 
@@ -53,9 +59,8 @@ module Dry
53
59
  # @param [Object] value
54
60
  #
55
61
  # @return [Dry::Types::Type]
56
- # @api public
57
62
  def Value(value)
58
- Nominal.new(value.class).constrained(eql: value)
63
+ Nominal(value.class).constrained(eql: value)
59
64
  end
60
65
 
61
66
  # Build a type with a single value
@@ -64,9 +69,8 @@ module Dry
64
69
  # @param [Object] object
65
70
  #
66
71
  # @return [Dry::Types::Type]
67
- # @api public
68
72
  def Constant(object)
69
- Nominal.new(object.class).constrained(is: object)
73
+ Nominal(object.class).constrained(is: object)
70
74
  end
71
75
 
72
76
  # Build a constructor type
@@ -77,9 +81,12 @@ module Dry
77
81
  # @param [#call,nil] block Value constructor
78
82
  #
79
83
  # @return [Dry::Types::Type]
80
- # @api public
81
84
  def Constructor(klass, cons = nil, &block)
82
- Nominal.new(klass).constructor(cons || block || klass.method(:new))
85
+ if klass.is_a?(Type)
86
+ klass.constructor(cons || block || klass.method(:new))
87
+ else
88
+ Nominal(klass).constructor(cons || block || klass.method(:new))
89
+ end
83
90
  end
84
91
 
85
92
  # Build a nominal type
@@ -87,9 +94,14 @@ module Dry
87
94
  # @param [Class] klass
88
95
  #
89
96
  # @return [Dry::Types::Type]
90
- # @api public
91
97
  def Nominal(klass)
92
- Nominal.new(klass)
98
+ if klass <= ::Array
99
+ Array.new(klass)
100
+ elsif klass <= ::Hash
101
+ Hash.new(klass)
102
+ else
103
+ Nominal.new(klass)
104
+ end
93
105
  end
94
106
 
95
107
  # Build a map type
@@ -102,9 +114,24 @@ module Dry
102
114
  # @param [Type] value_type Value type
103
115
  #
104
116
  # @return [Dry::Types::Map]
105
- # @api public
106
117
  def Map(key_type, value_type)
107
- Types['nominal.hash'].map(key_type, value_type)
118
+ Nominal(::Hash).map(key_type, value_type)
119
+ end
120
+
121
+ # Builds a constrained nominal type accepting any value that
122
+ # responds to given methods
123
+ #
124
+ # @example
125
+ # Types::Callable = Types.Interface(:call)
126
+ # Types::Contact = Types.Interface(:name, :address)
127
+ #
128
+ # @param methods [Array<String, Symbol>] Method names
129
+ #
130
+ # @return [Dry::Types::Contrained]
131
+ def Interface(*methods)
132
+ methods.reduce(Types['nominal.any']) do |type, method|
133
+ type.constrained(respond_to: method)
134
+ end
108
135
  end
109
136
  end
110
137
  end