dry-types 0.13.2 → 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +763 -233
  3. data/LICENSE +17 -17
  4. data/README.md +15 -13
  5. data/dry-types.gemspec +28 -28
  6. data/lib/dry-types.rb +3 -1
  7. data/lib/dry/types.rb +156 -76
  8. data/lib/dry/types/any.rb +32 -12
  9. data/lib/dry/types/array.rb +19 -6
  10. data/lib/dry/types/array/constructor.rb +32 -0
  11. data/lib/dry/types/array/member.rb +75 -16
  12. data/lib/dry/types/builder.rb +131 -15
  13. data/lib/dry/types/builder_methods.rb +49 -20
  14. data/lib/dry/types/coercions.rb +76 -22
  15. data/lib/dry/types/coercions/json.rb +43 -7
  16. data/lib/dry/types/coercions/params.rb +118 -31
  17. data/lib/dry/types/compat.rb +0 -2
  18. data/lib/dry/types/compiler.rb +56 -41
  19. data/lib/dry/types/constrained.rb +81 -32
  20. data/lib/dry/types/constrained/coercible.rb +36 -6
  21. data/lib/dry/types/constraints.rb +18 -4
  22. data/lib/dry/types/constructor.rb +127 -54
  23. data/lib/dry/types/constructor/function.rb +216 -0
  24. data/lib/dry/types/constructor/wrapper.rb +94 -0
  25. data/lib/dry/types/container.rb +7 -0
  26. data/lib/dry/types/core.rb +54 -21
  27. data/lib/dry/types/decorator.rb +38 -17
  28. data/lib/dry/types/default.rb +61 -16
  29. data/lib/dry/types/enum.rb +43 -20
  30. data/lib/dry/types/errors.rb +75 -9
  31. data/lib/dry/types/extensions.rb +7 -1
  32. data/lib/dry/types/extensions/maybe.rb +74 -16
  33. data/lib/dry/types/extensions/monads.rb +29 -0
  34. data/lib/dry/types/fn_container.rb +6 -1
  35. data/lib/dry/types/hash.rb +86 -67
  36. data/lib/dry/types/hash/constructor.rb +33 -0
  37. data/lib/dry/types/inflector.rb +3 -1
  38. data/lib/dry/types/json.rb +18 -16
  39. data/lib/dry/types/lax.rb +75 -0
  40. data/lib/dry/types/map.rb +76 -33
  41. data/lib/dry/types/meta.rb +51 -0
  42. data/lib/dry/types/module.rb +120 -0
  43. data/lib/dry/types/nominal.rb +210 -0
  44. data/lib/dry/types/options.rb +13 -26
  45. data/lib/dry/types/params.rb +39 -25
  46. data/lib/dry/types/predicate_inferrer.rb +238 -0
  47. data/lib/dry/types/predicate_registry.rb +34 -0
  48. data/lib/dry/types/primitive_inferrer.rb +97 -0
  49. data/lib/dry/types/printable.rb +16 -0
  50. data/lib/dry/types/printer.rb +315 -0
  51. data/lib/dry/types/result.rb +29 -3
  52. data/lib/dry/types/schema.rb +408 -0
  53. data/lib/dry/types/schema/key.rb +156 -0
  54. data/lib/dry/types/spec/types.rb +103 -33
  55. data/lib/dry/types/sum.rb +84 -35
  56. data/lib/dry/types/type.rb +49 -0
  57. data/lib/dry/types/version.rb +3 -1
  58. metadata +68 -79
  59. data/.gitignore +0 -10
  60. data/.rspec +0 -2
  61. data/.travis.yml +0 -29
  62. data/.yardopts +0 -5
  63. data/CONTRIBUTING.md +0 -29
  64. data/Gemfile +0 -24
  65. data/Rakefile +0 -20
  66. data/benchmarks/hash_schemas.rb +0 -51
  67. data/lib/dry/types/compat/form_types.rb +0 -27
  68. data/lib/dry/types/compat/int.rb +0 -14
  69. data/lib/dry/types/definition.rb +0 -113
  70. data/lib/dry/types/hash/schema.rb +0 -199
  71. data/lib/dry/types/hash/schema_builder.rb +0 -75
  72. data/lib/dry/types/safe.rb +0 -59
data/CONTRIBUTING.md DELETED
@@ -1,29 +0,0 @@
1
- # Issue Guidelines
2
-
3
- ## Reporting bugs
4
-
5
- If you found a bug, report an issue and describe what's the expected behavior versus what actually happens. If the bug causes a crash, attach a full backtrace. If possible, a reproduction script showing the problem is highly appreciated.
6
-
7
- ## Reporting feature requests
8
-
9
- Report a feature request **only after discussing it first on [discourse.dry-rb.org](https://discourse.dry-rb.org)** where it was accepted. Please provide a concise description of the feature, don't link to a discussion thread, and instead summarize what was discussed.
10
-
11
- ## Reporting questions, support requests, ideas, concerns etc.
12
-
13
- **PLEASE DON'T** - use [discourse.dry-rb.org](https://discourse.dry-rb.org) instead.
14
-
15
- # Pull Request Guidelines
16
-
17
- A Pull Request will only be accepted if it addresses a specific issue that was reported previously, or fixes typos, mistakes in documentation etc.
18
-
19
- Other requirements:
20
-
21
- 1) Do not open a pull request if you can't provide tests along with it. If you have problems writing tests, ask for help in the related issue.
22
- 2) Follow the style conventions of the surrounding code. In most cases, this is standard ruby style.
23
- 3) Add API documentation if it's a new feature
24
- 4) Update API documentation if it changes an existing feature
25
- 5) Bonus points for sending a PR to [github.com/dry-rb/dry-rb.org](github.com/dry-rb/dry-rb.org) which updates user documentation and guides
26
-
27
- # Asking for help
28
-
29
- If these guidelines aren't helpful, and you're stuck, please post a message on [discourse.dry-rb.org](https://discourse.dry-rb.org).
data/Gemfile DELETED
@@ -1,24 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- gemspec
4
-
5
- group :test do
6
- platform :mri do
7
- gem "codeclimate-test-reporter", require: false
8
- gem 'simplecov', require: false
9
- end
10
- end
11
-
12
- group :tools do
13
- gem 'pry-byebug', platform: :mri
14
- gem 'mutant'
15
- gem 'mutant-rspec'
16
- end
17
-
18
- group :benchmarks do
19
- gem 'benchmark-ips'
20
- gem 'virtus'
21
- gem 'fast_attributes'
22
- gem 'attrio'
23
- gem 'dry-struct'
24
- end
data/Rakefile DELETED
@@ -1,20 +0,0 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
3
-
4
- task :run_specs do
5
- require 'rspec/core'
6
-
7
- types_result = RSpec::Core::Runner.run(['spec/dry'])
8
- RSpec.clear_examples
9
-
10
- Dry::Types.load_extensions(:maybe)
11
- ext_result = RSpec::Core::Runner.run(['spec'])
12
-
13
- exit [types_result, ext_result].max
14
- end
15
-
16
- task default: :run_specs
17
-
18
- require 'yard'
19
- require 'yard/rake/yardoc_task'
20
- YARD::Rake::YardocTask.new(:doc)
@@ -1,51 +0,0 @@
1
- $LOAD_PATH.unshift('lib')
2
-
3
- require 'bundler/setup'
4
- require 'dry-types'
5
-
6
- module SchemaBench
7
- def self.hash_schema(type)
8
- Dry::Types['hash'].public_send(type,
9
- email: Dry::Types['string'],
10
- age: Dry::Types['params.integer'],
11
- admin: Dry::Types['params.bool'],
12
- address: Dry::Types['hash'].public_send(type,
13
- city: Dry::Types['string'],
14
- street: Dry::Types['string']
15
- )
16
- )
17
- end
18
-
19
- private_class_method(:hash_schema)
20
-
21
- SCHEMAS =
22
- Dry::Types::Hash
23
- .public_instance_methods(false)
24
- .map { |schema_type| [schema_type, hash_schema(schema_type)] }
25
- .to_h
26
-
27
- INPUT = {
28
- email: 'jane@doe.org',
29
- age: '20',
30
- admin: '1',
31
- address: { city: 'NYC', street: 'Street 1/2' }
32
- }
33
- end
34
-
35
- require 'benchmark/ips'
36
-
37
- Benchmark.ips do |x|
38
- SchemaBench::SCHEMAS.each do |schema_type, schema|
39
- x.report("#{schema_type}#call") do
40
- schema.call(SchemaBench::INPUT)
41
- end
42
- end
43
-
44
- SchemaBench::SCHEMAS.each do |schema_type, schema|
45
- x.report("#{schema_type}#try") do
46
- schema.try(SchemaBench::INPUT)
47
- end
48
- end
49
-
50
- x.compare!
51
- end
@@ -1,27 +0,0 @@
1
- require 'dry/core/deprecations'
2
-
3
- Dry::Core::Deprecations.warn('Form types were renamed to Params', tag: :'dry-types')
4
-
5
- module Dry
6
- module Types
7
- container.keys.grep(/^params\./).each do |key|
8
- next if key.start_with?('params.int')
9
- register(key.sub('params.', 'form.'), container[key])
10
- end
11
-
12
- register('form.int', self['params.integer'])
13
- register('form.integer', self['params.integer'])
14
-
15
- class Compiler
16
- def visit_form_hash(node)
17
- schema, meta = node
18
- merge_with('params.hash', :symbolized, schema).meta(meta)
19
- end
20
-
21
- def visit_form_array(node)
22
- member, meta = node
23
- registry['params.array'].of(visit(member)).meta(meta)
24
- end
25
- end
26
- end
27
- end
@@ -1,14 +0,0 @@
1
- require 'dry/core/deprecations'
2
-
3
- Dry::Core::Deprecations.warn('Int type was renamed to Integer', tag: :'dry-types')
4
-
5
- module Dry
6
- module Types
7
- register('int', self['integer'])
8
- register('strict.int', self['strict.integer'])
9
- register('coercible.int', self['coercible.integer'])
10
- register('optional.strict.int', self['optional.strict.integer'])
11
- register('optional.coercible.int', self['optional.coercible.integer'])
12
- register('params.int', self['params.integer'])
13
- end
14
- end
@@ -1,113 +0,0 @@
1
- require 'dry/types/builder'
2
- require 'dry/types/result'
3
- require 'dry/types/options'
4
-
5
- module Dry
6
- module Types
7
- class Definition
8
- include Type
9
- include Options
10
- include Builder
11
- include Dry::Equalizer(:primitive, :options, :meta)
12
-
13
- # @return [Class]
14
- attr_reader :primitive
15
-
16
- # @param [Class] primitive
17
- # @return [Type]
18
- def self.[](primitive)
19
- if primitive == ::Array
20
- Types::Array
21
- elsif primitive == ::Hash
22
- Types::Hash
23
- else
24
- self
25
- end
26
- end
27
-
28
- # @param [Type,Class] primitive
29
- # @param [Hash] options
30
- def initialize(primitive, options = {})
31
- super
32
- @primitive = primitive
33
- freeze
34
- end
35
-
36
- # @return [String]
37
- def name
38
- primitive.name
39
- end
40
-
41
- # @return [false]
42
- def default?
43
- false
44
- end
45
-
46
- # @return [false]
47
- def constrained?
48
- false
49
- end
50
-
51
- # @return [false]
52
- def optional?
53
- false
54
- end
55
-
56
- # @param [BasicObject] input
57
- # @return [BasicObject]
58
- def call(input)
59
- input
60
- end
61
- alias_method :[], :call
62
-
63
- # @param [Object] input
64
- # @param [#call,nil] block
65
- # @yieldparam [Failure] failure
66
- # @yieldreturn [Result]
67
- # @return [Result,Logic::Result] when a block is not provided
68
- # @return [nil] otherwise
69
- def try(input, &block)
70
- if valid?(input)
71
- success(input)
72
- else
73
- failure = failure(input, "#{input.inspect} must be an instance of #{primitive}")
74
- block ? yield(failure) : failure
75
- end
76
- end
77
-
78
- # @param (see Dry::Types::Success#initialize)
79
- # @return [Result::Success]
80
- def success(input)
81
- Result::Success.new(input)
82
- end
83
-
84
- # @param (see Failure#initialize)
85
- # @return [Result::Failure]
86
- def failure(input, error)
87
- Result::Failure.new(input, error)
88
- end
89
-
90
- # Checks whether value is of a #primitive class
91
- # @param [Object] value
92
- # @return [Boolean]
93
- def primitive?(value)
94
- value.is_a?(primitive)
95
- end
96
- alias_method :valid?, :primitive?
97
- alias_method :===, :primitive?
98
-
99
- # Return AST representation of a type definition
100
- #
101
- # @api public
102
- #
103
- # @return [Array]
104
- def to_ast(meta: true)
105
- [:definition, [primitive, meta ? self.meta : EMPTY_HASH]]
106
- end
107
- end
108
- end
109
- end
110
-
111
- require 'dry/types/array'
112
- require 'dry/types/hash'
113
- require 'dry/types/map'
@@ -1,199 +0,0 @@
1
- require 'dry/types/fn_container'
2
-
3
- module Dry
4
- module Types
5
- class Hash < Definition
6
- # The built-in Hash type has constructors that you can use to define
7
- # hashes with explicit schemas and coercible values using the built-in types.
8
- #
9
- # Basic {Schema} evaluates default values for keys missing in input hash
10
- # (see {Schema#resolve_missing_value})
11
- #
12
- # @see Dry::Types::Default#evaluate
13
- # @see Dry::Types::Default::Callable#evaluate
14
- class Schema < Hash
15
- NO_TRANSFORM = Dry::Types::FnContainer.register { |x| x }
16
- SYMBOLIZE_KEY = Dry::Types::FnContainer.register(:to_sym.to_proc)
17
-
18
- # @return [Hash{Symbol => Definition}]
19
- attr_reader :member_types
20
-
21
- # @return [#call]
22
- attr_reader :transform_key
23
-
24
- # @param [Class] _primitive
25
- # @param [Hash] options
26
- # @option options [Hash{Symbol => Definition}] :member_types
27
- # @option options [String] :key_transform_fn
28
- def initialize(_primitive, options)
29
- @member_types = options.fetch(:member_types)
30
-
31
- meta = options[:meta] || EMPTY_HASH
32
- key_fn = meta.fetch(:key_transform_fn, NO_TRANSFORM)
33
-
34
- @transform_key = Dry::Types::FnContainer[key_fn]
35
-
36
- super
37
- end
38
-
39
- # @param [Hash] hash
40
- # @return [Hash{Symbol => Object}]
41
- def call(hash)
42
- coerce(hash)
43
- end
44
- alias_method :[], :call
45
-
46
- # @param [Hash] hash
47
- # @yieldparam [Failure] failure
48
- # @yieldreturn [Result]
49
- # @return [Logic::Result]
50
- # @return [Object] if coercion fails and a block is given
51
- def try(hash)
52
- if hash.is_a?(::Hash)
53
- success = true
54
- output = {}
55
-
56
- begin
57
- result = try_coerce(hash) do |key, member_result|
58
- success &&= member_result.success?
59
- output[key] = member_result.input
60
-
61
- member_result
62
- end
63
- rescue ConstraintError, UnknownKeysError, SchemaError => e
64
- success = false
65
- result = e
66
- end
67
- else
68
- success = false
69
- output = hash
70
- result = "#{hash} must be a hash"
71
- end
72
-
73
- if success
74
- success(output)
75
- else
76
- failure = failure(output, result)
77
- block_given? ? yield(failure) : failure
78
- end
79
- end
80
-
81
- # @param meta [Boolean] Whether to dump the meta to the AST
82
- # @return [Array] An AST representation
83
- def to_ast(meta: true)
84
- [
85
- :hash_schema,
86
- [
87
- member_types.map { |name, member| [:member, [name, member.to_ast(meta: meta)]] },
88
- meta ? self.meta : EMPTY_HASH
89
- ]
90
- ]
91
- end
92
-
93
- # @param [Hash] hash
94
- # @return [Boolean]
95
- def valid?(hash)
96
- result = try(hash)
97
- result.success?
98
- end
99
- alias_method :===, :valid?
100
-
101
- # Whether the schema rejects unknown keys
102
- # @return [Boolean]
103
- def strict?
104
- meta.fetch(:strict, false)
105
- end
106
-
107
- # Make the schema intolerant to unknown keys
108
- # @return [Schema]
109
- def strict
110
- meta(strict: true)
111
- end
112
-
113
- # Injects a key transformation function
114
- # @param [#call,nil] proc
115
- # @param [#call,nil] block
116
- # @return [Schema]
117
- def with_key_transform(proc = nil, &block)
118
- fn = proc || block
119
-
120
- if fn.nil?
121
- raise ArgumentError, "a block or callable argument is required"
122
- end
123
-
124
- handle = Dry::Types::FnContainer.register(fn)
125
- meta(key_transform_fn: handle)
126
- end
127
-
128
- # @param [{Symbol => Definition}] type_map
129
- # @return [Schema]
130
- def schema(type_map)
131
- member_types = self.member_types.merge(transform_types(type_map))
132
- Schema.new(primitive, **options, member_types: member_types, meta: meta)
133
- end
134
-
135
- private
136
-
137
- def resolve(hash)
138
- result = {}
139
-
140
- hash.each do |key, value|
141
- k = transform_key.(key)
142
-
143
- if member_types.key?(k)
144
- result[k] = yield(member_types[k], k, value)
145
- elsif strict?
146
- raise UnknownKeysError.new(*unexpected_keys(hash.keys))
147
- end
148
- end
149
-
150
- if result.size < member_types.size
151
- resolve_missing_keys(result, &Proc.new)
152
- end
153
-
154
- result
155
- end
156
-
157
- def resolve_missing_keys(result)
158
- member_types.each do |k, type|
159
- next if result.key?(k)
160
-
161
- if type.default?
162
- result[k] = yield(type, k, Undefined)
163
- elsif !type.meta[:omittable]
164
- raise MissingKeyError, k
165
- end
166
- end
167
- end
168
-
169
- # @param keys [Array<Symbol>]
170
- # @return [Array<Symbol>]
171
- def unexpected_keys(keys)
172
- keys.map(&transform_key) - member_types.keys
173
- end
174
-
175
- # @param [Hash] hash
176
- # @return [Hash{Symbol => Object}]
177
- def try_coerce(hash)
178
- resolve(hash) do |type, key, value|
179
- yield(key, type.try(value))
180
- end
181
- end
182
-
183
- # @param [Hash] hash
184
- # @return [Hash{Symbol => Object}]
185
- def coerce(hash)
186
- resolve(hash) do |type, key, value|
187
- begin
188
- type.call(value)
189
- rescue ConstraintError => e
190
- raise SchemaError.new(key, value, e.result)
191
- rescue TypeError, ArgumentError => e
192
- raise SchemaError.new(key, value, e.message)
193
- end
194
- end
195
- end
196
- end
197
- end
198
- end
199
- end