dry-schema 0.1.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +3 -0
- data/LICENSE +20 -0
- data/README.md +21 -0
- data/config/errors.yml +91 -0
- data/lib/dry-schema.rb +1 -0
- data/lib/dry/schema.rb +51 -0
- data/lib/dry/schema/compiler.rb +31 -0
- data/lib/dry/schema/config.rb +52 -0
- data/lib/dry/schema/constants.rb +13 -0
- data/lib/dry/schema/dsl.rb +382 -0
- data/lib/dry/schema/extensions.rb +3 -0
- data/lib/dry/schema/extensions/monads.rb +18 -0
- data/lib/dry/schema/json.rb +16 -0
- data/lib/dry/schema/key.rb +166 -0
- data/lib/dry/schema/key_coercer.rb +37 -0
- data/lib/dry/schema/key_map.rb +133 -0
- data/lib/dry/schema/macros.rb +6 -0
- data/lib/dry/schema/macros/core.rb +51 -0
- data/lib/dry/schema/macros/dsl.rb +74 -0
- data/lib/dry/schema/macros/each.rb +18 -0
- data/lib/dry/schema/macros/filled.rb +24 -0
- data/lib/dry/schema/macros/hash.rb +46 -0
- data/lib/dry/schema/macros/key.rb +137 -0
- data/lib/dry/schema/macros/maybe.rb +37 -0
- data/lib/dry/schema/macros/optional.rb +17 -0
- data/lib/dry/schema/macros/required.rb +17 -0
- data/lib/dry/schema/macros/value.rb +41 -0
- data/lib/dry/schema/message.rb +103 -0
- data/lib/dry/schema/message_compiler.rb +193 -0
- data/lib/dry/schema/message_compiler/visitor_opts.rb +30 -0
- data/lib/dry/schema/message_set.rb +123 -0
- data/lib/dry/schema/messages.rb +42 -0
- data/lib/dry/schema/messages/abstract.rb +143 -0
- data/lib/dry/schema/messages/i18n.rb +60 -0
- data/lib/dry/schema/messages/namespaced.rb +53 -0
- data/lib/dry/schema/messages/yaml.rb +82 -0
- data/lib/dry/schema/params.rb +16 -0
- data/lib/dry/schema/predicate.rb +80 -0
- data/lib/dry/schema/predicate_inferrer.rb +49 -0
- data/lib/dry/schema/predicate_registry.rb +38 -0
- data/lib/dry/schema/processor.rb +151 -0
- data/lib/dry/schema/result.rb +164 -0
- data/lib/dry/schema/rule_applier.rb +45 -0
- data/lib/dry/schema/trace.rb +103 -0
- data/lib/dry/schema/type_registry.rb +42 -0
- data/lib/dry/schema/types.rb +12 -0
- data/lib/dry/schema/value_coercer.rb +27 -0
- data/lib/dry/schema/version.rb +5 -0
- metadata +255 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b804e7069cd7d8764f82ce208e38768fa9975e6d1460e1e44917d5c0eaeee911
|
4
|
+
data.tar.gz: 6814f96c49db2634895c20faf7b331feccbdcdcbc83ff89292f655acd6c0adfc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 80d4cc2626025ef517e48bba83026c1bba7ef5797fa25e2543589ed503ec9d7142e799ed9713d4452b7a1f1b2d2988b4c6aeb3172230ec6fd4987d58c8621775
|
7
|
+
data.tar.gz: f35d2b94570ec416b6fcb0457a4e685f37ff3a265a011a4016af611307e2d8d1b8b03dc9e4b59a63b5f2cf55aacf8ceabac0a5d689e73f14e4aaa5cef96e1fa4
|
data/CHANGELOG.md
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Dryrb Team
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
[gem]: https://rubygems.org/gems/dry-schema
|
2
|
+
[travis]: https://travis-ci.org/dry-rb/dry-schema
|
3
|
+
[codeclimate]: https://codeclimate.com/github/dry-rb/dry-schema
|
4
|
+
[coveralls]: https://coveralls.io/r/dry-rb/dry-schema
|
5
|
+
[inchpages]: http://inch-ci.org/github/dry-rb/dry-schema
|
6
|
+
|
7
|
+
# dry-schema [](https://gitter.im/dry-rb/chat)
|
8
|
+
|
9
|
+
[][gem]
|
10
|
+
[][travis]
|
11
|
+
[][codeclimate]
|
12
|
+
[][codeclimate]
|
13
|
+
[][inchpages]
|
14
|
+
|
15
|
+
<!-- ## Links
|
16
|
+
|
17
|
+
* [Documentation](http://dry-rb.org/gems/dry-schema) -->
|
18
|
+
|
19
|
+
## License
|
20
|
+
|
21
|
+
See `LICENSE` file.
|
data/config/errors.yml
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
en:
|
2
|
+
errors:
|
3
|
+
or: "or"
|
4
|
+
array?: "must be an array"
|
5
|
+
|
6
|
+
empty?: "must be empty"
|
7
|
+
|
8
|
+
excludes?: "must not include %{value}"
|
9
|
+
|
10
|
+
excluded_from?:
|
11
|
+
arg:
|
12
|
+
default: "must not be one of: %{list}"
|
13
|
+
range: "must not be one of: %{list_left} - %{list_right}"
|
14
|
+
exclusion?: "must not be one of: %{list}"
|
15
|
+
|
16
|
+
eql?: "must be equal to %{left}"
|
17
|
+
|
18
|
+
not_eql?: "must not be equal to %{left}"
|
19
|
+
|
20
|
+
filled?: "must be filled"
|
21
|
+
|
22
|
+
format?: "is in invalid format"
|
23
|
+
|
24
|
+
number?: "must be a number"
|
25
|
+
|
26
|
+
odd?: "must be odd"
|
27
|
+
|
28
|
+
even?: "must be even"
|
29
|
+
|
30
|
+
gt?: "must be greater than %{num}"
|
31
|
+
|
32
|
+
gteq?: "must be greater than or equal to %{num}"
|
33
|
+
|
34
|
+
hash?: "must be a hash"
|
35
|
+
|
36
|
+
included_in?:
|
37
|
+
arg:
|
38
|
+
default: "must be one of: %{list}"
|
39
|
+
range: "must be one of: %{list_left} - %{list_right}"
|
40
|
+
inclusion?: "must be one of: %{list}"
|
41
|
+
|
42
|
+
includes?: "must include %{value}"
|
43
|
+
|
44
|
+
bool?: "must be boolean"
|
45
|
+
|
46
|
+
true?: "must be true"
|
47
|
+
|
48
|
+
false?: "must be false"
|
49
|
+
|
50
|
+
int?: "must be an integer"
|
51
|
+
|
52
|
+
float?: "must be a float"
|
53
|
+
|
54
|
+
decimal?: "must be a decimal"
|
55
|
+
|
56
|
+
date?: "must be a date"
|
57
|
+
|
58
|
+
date_time?: "must be a date time"
|
59
|
+
|
60
|
+
time?: "must be a time"
|
61
|
+
|
62
|
+
key?: "is missing"
|
63
|
+
|
64
|
+
attr?: "is missing"
|
65
|
+
|
66
|
+
lt?: "must be less than %{num}"
|
67
|
+
|
68
|
+
lteq?: "must be less than or equal to %{num}"
|
69
|
+
|
70
|
+
max_size?: "size cannot be greater than %{num}"
|
71
|
+
|
72
|
+
min_size?: "size cannot be less than %{num}"
|
73
|
+
|
74
|
+
nil?: "cannot be defined"
|
75
|
+
|
76
|
+
str?: "must be a string"
|
77
|
+
|
78
|
+
type?: "must be %{type}"
|
79
|
+
|
80
|
+
size?:
|
81
|
+
arg:
|
82
|
+
default: "size must be %{size}"
|
83
|
+
range: "size must be within %{size_left} - %{size_right}"
|
84
|
+
|
85
|
+
value:
|
86
|
+
string:
|
87
|
+
arg:
|
88
|
+
default: "length must be %{size}"
|
89
|
+
range: "length must be within %{size_left} - %{size_right}"
|
90
|
+
not:
|
91
|
+
empty?: "cannot be empty"
|
data/lib/dry-schema.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'dry/schema'
|
data/lib/dry/schema.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'dry/core/extensions'
|
2
|
+
|
3
|
+
require 'dry/schema/constants'
|
4
|
+
require 'dry/schema/dsl'
|
5
|
+
require 'dry/schema/params'
|
6
|
+
require 'dry/schema/json'
|
7
|
+
|
8
|
+
module Dry
|
9
|
+
module Schema
|
10
|
+
extend Dry::Core::Extensions
|
11
|
+
|
12
|
+
# Define a schema
|
13
|
+
#
|
14
|
+
# @return [Processor]
|
15
|
+
#
|
16
|
+
# @api public
|
17
|
+
def self.define(options = EMPTY_HASH, &block)
|
18
|
+
DSL.new(options, &block).call
|
19
|
+
end
|
20
|
+
|
21
|
+
# Define a param schema
|
22
|
+
#
|
23
|
+
# @return [Params]
|
24
|
+
#
|
25
|
+
# @api public
|
26
|
+
def self.Params(**options, &block)
|
27
|
+
define(**options, processor_type: Params, &block)
|
28
|
+
end
|
29
|
+
singleton_class.send(:alias_method, :Form, :Params)
|
30
|
+
|
31
|
+
# Define a JSON schema
|
32
|
+
#
|
33
|
+
# @return [JSON]
|
34
|
+
#
|
35
|
+
# @api public
|
36
|
+
def self.JSON(**options, &block)
|
37
|
+
define(**options, processor_type: JSON, &block)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Return configured paths to message files
|
41
|
+
#
|
42
|
+
# @return [Array<String>]
|
43
|
+
#
|
44
|
+
# @api public
|
45
|
+
def self.messages_paths
|
46
|
+
Messages::Abstract.config.paths
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
require 'dry/schema/extensions'
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'dry/logic/rule_compiler'
|
2
|
+
require 'dry/schema/predicate_registry'
|
3
|
+
|
4
|
+
module Dry
|
5
|
+
module Schema
|
6
|
+
# Extended rule compiler used internally by the DSL
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
class Compiler < Logic::RuleCompiler
|
10
|
+
# Builds a default compiler instance with custom predicate registry
|
11
|
+
#
|
12
|
+
# @return [Compiler]
|
13
|
+
#
|
14
|
+
# @api private
|
15
|
+
def self.new(predicates = PredicateRegistry.new)
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
# Return true if a given predicate is supported by this compiler
|
20
|
+
#
|
21
|
+
# @param [Symbol] predicate
|
22
|
+
#
|
23
|
+
# @return [Boolean]
|
24
|
+
#
|
25
|
+
# @api private
|
26
|
+
def supports?(predicate)
|
27
|
+
predicates.key?(predicate)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
require 'dry/configurable'
|
3
|
+
|
4
|
+
require 'dry/schema/predicate_registry'
|
5
|
+
|
6
|
+
module Dry
|
7
|
+
module Schema
|
8
|
+
# Schema definition configuration class
|
9
|
+
#
|
10
|
+
# @see DSL#configure
|
11
|
+
#
|
12
|
+
# @api public
|
13
|
+
class Config < SimpleDelegator
|
14
|
+
extend Dry::Configurable
|
15
|
+
|
16
|
+
setting :predicates, Schema::PredicateRegistry.new
|
17
|
+
setting :messages, :yaml
|
18
|
+
setting :messages_file
|
19
|
+
setting :namespace
|
20
|
+
setting :rules, {}
|
21
|
+
|
22
|
+
# Build a new config object with defaults filled in
|
23
|
+
#
|
24
|
+
# @api private
|
25
|
+
def self.new
|
26
|
+
super(struct.new(*settings.map { |key| config.public_send(key) }))
|
27
|
+
end
|
28
|
+
|
29
|
+
# Build a struct with defined settings
|
30
|
+
#
|
31
|
+
# @return [Struct]
|
32
|
+
#
|
33
|
+
# @api private
|
34
|
+
def self.struct
|
35
|
+
::Struct.new(*settings)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Expose configurable object to the provided block
|
39
|
+
#
|
40
|
+
# This method is used by `DSL#configure`
|
41
|
+
#
|
42
|
+
# @return [Config]
|
43
|
+
#
|
44
|
+
# @api private
|
45
|
+
def configure(&block)
|
46
|
+
yield(__getobj__)
|
47
|
+
values.freeze
|
48
|
+
freeze
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,382 @@
|
|
1
|
+
require 'dry/initializer'
|
2
|
+
|
3
|
+
require 'dry/schema/constants'
|
4
|
+
require 'dry/schema/config'
|
5
|
+
require 'dry/schema/compiler'
|
6
|
+
require 'dry/schema/types'
|
7
|
+
require 'dry/schema/macros'
|
8
|
+
|
9
|
+
require 'dry/schema/processor'
|
10
|
+
require 'dry/schema/key_map'
|
11
|
+
require 'dry/schema/key_coercer'
|
12
|
+
require 'dry/schema/value_coercer'
|
13
|
+
require 'dry/schema/rule_applier'
|
14
|
+
|
15
|
+
module Dry
|
16
|
+
module Schema
|
17
|
+
# The schema definition DSL class
|
18
|
+
#
|
19
|
+
# The DSL is exposed by:
|
20
|
+
# - `Schema.define`
|
21
|
+
# - `Schema.Params`
|
22
|
+
# - `Schema.JSON`
|
23
|
+
# - `Schema::Params.define` - use with sub-classes
|
24
|
+
# - `Schema::JSON.define` - use with sub-classes
|
25
|
+
#
|
26
|
+
# @example class-based definition
|
27
|
+
# class UserSchema < Dry::Schema::Params
|
28
|
+
# define do
|
29
|
+
# required(:name).filled
|
30
|
+
# required(:age).filled(:integer, gt: 18)
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# user_schema = UserSchema.new
|
35
|
+
# user_schema.(name: 'Jame', age: 21)
|
36
|
+
#
|
37
|
+
# @example instance-based definition shortcut
|
38
|
+
# UserSchema = Dry::Schema.Params do
|
39
|
+
# required(:name).filled
|
40
|
+
# required(:age).filled(:integer, gt: 18)
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# UserSchema.(name: 'Jame', age: 21)
|
44
|
+
#
|
45
|
+
# @api public
|
46
|
+
class DSL
|
47
|
+
Types = Schema::Types
|
48
|
+
|
49
|
+
extend Dry::Initializer
|
50
|
+
|
51
|
+
include ::Dry::Equalizer(:options)
|
52
|
+
|
53
|
+
# @!attribute [r] compiler
|
54
|
+
# @return [Compiler] The rule compiler object
|
55
|
+
option :compiler, default: -> { Compiler.new }
|
56
|
+
|
57
|
+
# @!attribute [r] processor_type
|
58
|
+
# @return [Compiler] The type of the processor (Params, JSON, or a custom sub-class)
|
59
|
+
option :processor_type, default: -> { Processor }
|
60
|
+
|
61
|
+
# @!attribute [r] macros
|
62
|
+
# @return [Array] An array with macros defined within the DSL
|
63
|
+
option :macros, default: -> { EMPTY_ARRAY.dup }
|
64
|
+
|
65
|
+
# @!attribute [r] types
|
66
|
+
# @return [Compiler] A key=>type map defined within the DSL
|
67
|
+
option :types, default: -> { EMPTY_HASH.dup }
|
68
|
+
|
69
|
+
# @!attribute [r] parent
|
70
|
+
# @return [DSL] An optional parent DSL object that will be used to merge keys and rules
|
71
|
+
option :parent, optional: true
|
72
|
+
|
73
|
+
# @!attribute [r] config
|
74
|
+
# @return [Config] Configuration object exposed via `#configure` method
|
75
|
+
option :config, optional: true, default: -> { Config.new }
|
76
|
+
|
77
|
+
# Build a new DSL object and evaluate provided block
|
78
|
+
#
|
79
|
+
# @param [Hash] options
|
80
|
+
# @option options [Class] :processor The processor type (`Params`, `JSON` or a custom sub-class)
|
81
|
+
# @option options [Compiler] :compiler An instance of a rule compiler (must be compatible with `Schema::Compiler`) (optional)
|
82
|
+
# @option options [DSL] :parent An instance of the parent DSL (optional)
|
83
|
+
# @option options [Config] :config A configuration object (optional)
|
84
|
+
#
|
85
|
+
# @see Schema.define
|
86
|
+
# @see Schema.Params
|
87
|
+
# @see Schema.JSON
|
88
|
+
# @see Processor.define
|
89
|
+
#
|
90
|
+
# @return [DSL]
|
91
|
+
#
|
92
|
+
# @api public
|
93
|
+
def self.new(options = EMPTY_HASH, &block)
|
94
|
+
dsl = super
|
95
|
+
dsl.instance_eval(&block) if block
|
96
|
+
dsl
|
97
|
+
end
|
98
|
+
|
99
|
+
# Provide customized configuration for your schema
|
100
|
+
#
|
101
|
+
# @example
|
102
|
+
# Dry::Schema.define do
|
103
|
+
# configure do |config|
|
104
|
+
# config.messages = :i18n
|
105
|
+
# end
|
106
|
+
# end
|
107
|
+
#
|
108
|
+
# @see Config
|
109
|
+
#
|
110
|
+
# @return [DSL]
|
111
|
+
#
|
112
|
+
# @api public
|
113
|
+
def configure(&block)
|
114
|
+
config.configure(&block)
|
115
|
+
self
|
116
|
+
end
|
117
|
+
|
118
|
+
# Define a required key
|
119
|
+
#
|
120
|
+
# @example
|
121
|
+
# required(:name).filled
|
122
|
+
#
|
123
|
+
# required(:age).value(:integer)
|
124
|
+
#
|
125
|
+
# required(:user_limit).value(:integer, gt?: 0)
|
126
|
+
#
|
127
|
+
# required(:tags).filled { array? | str? }
|
128
|
+
#
|
129
|
+
# @param [Symbol] name The key name
|
130
|
+
#
|
131
|
+
# @return [Macros::Required]
|
132
|
+
#
|
133
|
+
# @api public
|
134
|
+
def required(name, &block)
|
135
|
+
key(name, macro: Macros::Required, &block)
|
136
|
+
end
|
137
|
+
|
138
|
+
# Define an optional key
|
139
|
+
#
|
140
|
+
# This works exactly the same as `required` except that if a key is not present
|
141
|
+
# rules will not be applied
|
142
|
+
#
|
143
|
+
# @see DSL#required
|
144
|
+
#
|
145
|
+
# @param [Symbol] name The key name
|
146
|
+
#
|
147
|
+
# @return [Macros::Optional]
|
148
|
+
#
|
149
|
+
# @api public
|
150
|
+
def optional(name, &block)
|
151
|
+
key(name, macro: Macros::Optional, &block)
|
152
|
+
end
|
153
|
+
|
154
|
+
# A generic method for defining keys
|
155
|
+
#
|
156
|
+
# @param [Symbol] name The key name
|
157
|
+
# @param [Class] macro The macro sub-class (ie `Macros::Required` or any other `Macros::Key` subclass)
|
158
|
+
#
|
159
|
+
# @return [Macros::Key]
|
160
|
+
#
|
161
|
+
# @api public
|
162
|
+
def key(name, macro:, &block)
|
163
|
+
set_type(name, Types::Any)
|
164
|
+
|
165
|
+
macro = macro.new(
|
166
|
+
name: name,
|
167
|
+
compiler: compiler,
|
168
|
+
schema_dsl: self,
|
169
|
+
filter_schema: filter_schema
|
170
|
+
)
|
171
|
+
|
172
|
+
macro.value(&block) if block
|
173
|
+
macros << macro
|
174
|
+
macro
|
175
|
+
end
|
176
|
+
|
177
|
+
# Build a processor based on DSL's definitions
|
178
|
+
#
|
179
|
+
# @return [Processor]
|
180
|
+
#
|
181
|
+
# @api private
|
182
|
+
def call
|
183
|
+
steps = [key_coercer]
|
184
|
+
steps << filter_schema.rule_applier if filter_rules?
|
185
|
+
steps << value_coercer << rule_applier
|
186
|
+
|
187
|
+
processor_type.new { |processor| steps.each { |step| processor << step } }
|
188
|
+
end
|
189
|
+
|
190
|
+
# Cast this DSL into a rule object
|
191
|
+
#
|
192
|
+
# @return [RuleApplier]
|
193
|
+
def to_rule
|
194
|
+
call.to_rule
|
195
|
+
end
|
196
|
+
|
197
|
+
# A shortcut for defining an array type with a member
|
198
|
+
#
|
199
|
+
# @example
|
200
|
+
# required(:tags).filled(array[:string])
|
201
|
+
#
|
202
|
+
# @return [Dry::Types::Array::Member]
|
203
|
+
#
|
204
|
+
# @api public
|
205
|
+
def array
|
206
|
+
-> member_type { type_registry["array"].of(resolve_type(member_type)) }
|
207
|
+
end
|
208
|
+
|
209
|
+
# Return type schema used by the value coercer
|
210
|
+
#
|
211
|
+
# @return [Dry::Types::Safe]
|
212
|
+
#
|
213
|
+
# @api private
|
214
|
+
def type_schema
|
215
|
+
type_registry["hash"].schema(types.merge(parent_types)).safe
|
216
|
+
end
|
217
|
+
|
218
|
+
# Return a new DSL instance using the same processor type
|
219
|
+
#
|
220
|
+
# @return [Dry::Types::Safe]
|
221
|
+
#
|
222
|
+
# @api private
|
223
|
+
def new(&block)
|
224
|
+
self.class.new(processor_type: processor_type, &block)
|
225
|
+
end
|
226
|
+
|
227
|
+
# Set a type for the given key name
|
228
|
+
#
|
229
|
+
# @param [Symbol] name The key name
|
230
|
+
# @param [Symbol, Array<Symbol>, Dry::Types::Type] spec The type spec or a type object
|
231
|
+
#
|
232
|
+
# @return [Dry::Types::Safe]
|
233
|
+
#
|
234
|
+
# @api private
|
235
|
+
def set_type(name, spec)
|
236
|
+
type = resolve_type(spec)
|
237
|
+
meta = { omittable: true, maybe: maybe?(type) }
|
238
|
+
|
239
|
+
types[name] = type.meta(meta)
|
240
|
+
end
|
241
|
+
|
242
|
+
# Check if the given type is a maybe sum
|
243
|
+
#
|
244
|
+
# @api private
|
245
|
+
def maybe?(type)
|
246
|
+
type.is_a?(Dry::Types::Sum) && type.left.primitive.equal?(NilClass)
|
247
|
+
end
|
248
|
+
|
249
|
+
protected
|
250
|
+
|
251
|
+
# Build a rule applier
|
252
|
+
#
|
253
|
+
# @return [RuleApplier]
|
254
|
+
#
|
255
|
+
# @api protected
|
256
|
+
def rule_applier
|
257
|
+
RuleApplier.new(rules, config: config)
|
258
|
+
end
|
259
|
+
|
260
|
+
# Build rules from defined macros
|
261
|
+
#
|
262
|
+
# @see #rule_applier
|
263
|
+
#
|
264
|
+
# @api protected
|
265
|
+
def rules
|
266
|
+
macros.map { |m| [m.name, m.to_rule] }.to_h.merge(parent_rules)
|
267
|
+
end
|
268
|
+
|
269
|
+
# Build a key map from defined types
|
270
|
+
#
|
271
|
+
# @api protected
|
272
|
+
def key_map(types = self.types)
|
273
|
+
keys = types.keys.each_with_object([]) { |key_name, arr|
|
274
|
+
arr << key_spec(key_name, types[key_name])
|
275
|
+
}
|
276
|
+
km = KeyMap.new(keys)
|
277
|
+
|
278
|
+
if key_map_type
|
279
|
+
km.public_send(key_map_type)
|
280
|
+
else
|
281
|
+
km
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
private
|
286
|
+
|
287
|
+
# Check if any filter rules were defined
|
288
|
+
#
|
289
|
+
# @api private
|
290
|
+
def filter_rules?
|
291
|
+
instance_variable_defined?('@__filter_schema__') && !filter_schema.macros.empty?
|
292
|
+
end
|
293
|
+
|
294
|
+
# Build an input schema DSL used by `filter` API
|
295
|
+
#
|
296
|
+
# @see Macros::Value#filter
|
297
|
+
#
|
298
|
+
# @api private
|
299
|
+
def filter_schema
|
300
|
+
@__filter_schema__ ||= new
|
301
|
+
end
|
302
|
+
|
303
|
+
# Build a key coercer
|
304
|
+
#
|
305
|
+
# @return [KeyCoercer]
|
306
|
+
#
|
307
|
+
# @api private
|
308
|
+
def key_coercer
|
309
|
+
KeyCoercer.symbolized(key_map + parent_key_map)
|
310
|
+
end
|
311
|
+
|
312
|
+
# Build a value coercer
|
313
|
+
#
|
314
|
+
# @return [ValueCoercer]
|
315
|
+
#
|
316
|
+
# @api private
|
317
|
+
def value_coercer
|
318
|
+
ValueCoercer.new(type_schema)
|
319
|
+
end
|
320
|
+
|
321
|
+
# Return type registry configured by the processor type
|
322
|
+
#
|
323
|
+
# @api private
|
324
|
+
def type_registry
|
325
|
+
processor_type.config.type_registry
|
326
|
+
end
|
327
|
+
|
328
|
+
# Return key map type configured by the processor type
|
329
|
+
#
|
330
|
+
# @api private
|
331
|
+
def key_map_type
|
332
|
+
processor_type.config.key_map_type
|
333
|
+
end
|
334
|
+
|
335
|
+
# Build a key spec needed by the key map
|
336
|
+
#
|
337
|
+
# @api private
|
338
|
+
def key_spec(name, type)
|
339
|
+
if type.respond_to?(:member_types)
|
340
|
+
{ name => key_map(type.member_types) }
|
341
|
+
elsif type.respond_to?(:member)
|
342
|
+
kv = key_spec(name, type.member)
|
343
|
+
kv.equal?(name) ? name : kv.flatten(1)
|
344
|
+
else
|
345
|
+
name
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
# Resolve type object from the provided spec
|
350
|
+
#
|
351
|
+
# @param [Symbol, Array<Symbol>, Dry::Types::Type]
|
352
|
+
#
|
353
|
+
# @return [Dry::Types::Type]
|
354
|
+
#
|
355
|
+
# @api private
|
356
|
+
def resolve_type(spec)
|
357
|
+
case spec
|
358
|
+
when ::Dry::Types::Type then spec
|
359
|
+
when ::Array then spec.map { |s| resolve_type(s) }.reduce(:|)
|
360
|
+
else
|
361
|
+
type_registry[spec]
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
# @api private
|
366
|
+
def parent_rules
|
367
|
+
parent&.rules || EMPTY_HASH
|
368
|
+
end
|
369
|
+
|
370
|
+
# @api private
|
371
|
+
def parent_types
|
372
|
+
# TODO: this is awful, it'd be nice if we had `Dry::Types::Hash::Schema#merge`
|
373
|
+
parent&.type_schema&.member_types || EMPTY_HASH
|
374
|
+
end
|
375
|
+
|
376
|
+
# @api private
|
377
|
+
def parent_key_map
|
378
|
+
parent&.key_map || EMPTY_ARRAY
|
379
|
+
end
|
380
|
+
end
|
381
|
+
end
|
382
|
+
end
|