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,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