dry-validation 0.13.3 → 1.3.1

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 (206) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +233 -12
  3. data/LICENSE +1 -1
  4. data/README.md +13 -9
  5. data/config/errors.yml +3 -88
  6. data/lib/dry-validation.rb +2 -0
  7. data/lib/dry/validation.rb +47 -28
  8. data/lib/dry/validation/config.rb +24 -0
  9. data/lib/dry/validation/constants.rb +43 -0
  10. data/lib/dry/validation/contract.rb +160 -0
  11. data/lib/dry/validation/contract/class_interface.rb +223 -0
  12. data/lib/dry/validation/evaluator.rb +197 -0
  13. data/lib/dry/validation/extensions/hints.rb +69 -0
  14. data/lib/dry/validation/extensions/monads.rb +23 -7
  15. data/lib/dry/validation/extensions/predicates_as_macros.rb +75 -0
  16. data/lib/dry/validation/failures.rb +58 -0
  17. data/lib/dry/validation/function.rb +44 -0
  18. data/lib/dry/validation/macro.rb +38 -0
  19. data/lib/dry/validation/macros.rb +104 -0
  20. data/lib/dry/validation/message.rb +79 -79
  21. data/lib/dry/validation/message_set.rb +108 -88
  22. data/lib/dry/validation/messages/resolver.rb +79 -0
  23. data/lib/dry/validation/result.rb +154 -42
  24. data/lib/dry/validation/rule.rb +129 -0
  25. data/lib/dry/validation/schema_ext.rb +46 -0
  26. data/lib/dry/validation/values.rb +94 -0
  27. data/lib/dry/validation/version.rb +3 -1
  28. metadata +41 -336
  29. data/.codeclimate.yml +0 -17
  30. data/.gitignore +0 -9
  31. data/.rspec +0 -3
  32. data/.travis.yml +0 -29
  33. data/CONTRIBUTING.md +0 -31
  34. data/Gemfile +0 -25
  35. data/Rakefile +0 -22
  36. data/benchmarks/benchmark_form_invalid.rb +0 -64
  37. data/benchmarks/benchmark_form_valid.rb +0 -64
  38. data/benchmarks/benchmark_schema_invalid_huge.rb +0 -52
  39. data/benchmarks/profile_schema_call_invalid.rb +0 -20
  40. data/benchmarks/profile_schema_call_valid.rb +0 -20
  41. data/benchmarks/profile_schema_definition.rb +0 -14
  42. data/benchmarks/profile_schema_huge_invalid.rb +0 -30
  43. data/benchmarks/profile_schema_messages_invalid.rb +0 -20
  44. data/benchmarks/suite.rb +0 -5
  45. data/dry-validation.gemspec +0 -28
  46. data/examples/basic.rb +0 -15
  47. data/examples/each.rb +0 -14
  48. data/examples/json.rb +0 -12
  49. data/examples/multiple.rb +0 -27
  50. data/examples/nested.rb +0 -22
  51. data/examples/params.rb +0 -11
  52. data/lib/dry/validation/compat/form.rb +0 -67
  53. data/lib/dry/validation/deprecations.rb +0 -24
  54. data/lib/dry/validation/executor.rb +0 -91
  55. data/lib/dry/validation/extensions.rb +0 -7
  56. data/lib/dry/validation/extensions/struct.rb +0 -32
  57. data/lib/dry/validation/input_processor_compiler.rb +0 -137
  58. data/lib/dry/validation/input_processor_compiler/json.rb +0 -45
  59. data/lib/dry/validation/input_processor_compiler/params.rb +0 -49
  60. data/lib/dry/validation/input_processor_compiler/sanitizer.rb +0 -47
  61. data/lib/dry/validation/message_compiler.rb +0 -188
  62. data/lib/dry/validation/message_compiler/visitor_opts.rb +0 -37
  63. data/lib/dry/validation/messages.rb +0 -14
  64. data/lib/dry/validation/messages/abstract.rb +0 -119
  65. data/lib/dry/validation/messages/i18n.rb +0 -47
  66. data/lib/dry/validation/messages/namespaced.rb +0 -39
  67. data/lib/dry/validation/messages/yaml.rb +0 -61
  68. data/lib/dry/validation/predicate_registry.rb +0 -115
  69. data/lib/dry/validation/predicates.rb +0 -19
  70. data/lib/dry/validation/schema.rb +0 -126
  71. data/lib/dry/validation/schema/check.rb +0 -37
  72. data/lib/dry/validation/schema/class_interface.rb +0 -190
  73. data/lib/dry/validation/schema/deprecated.rb +0 -30
  74. data/lib/dry/validation/schema/dsl.rb +0 -118
  75. data/lib/dry/validation/schema/form.rb +0 -9
  76. data/lib/dry/validation/schema/json.rb +0 -21
  77. data/lib/dry/validation/schema/key.rb +0 -71
  78. data/lib/dry/validation/schema/params.rb +0 -22
  79. data/lib/dry/validation/schema/rule.rb +0 -202
  80. data/lib/dry/validation/schema/value.rb +0 -211
  81. data/lib/dry/validation/schema_compiler.rb +0 -81
  82. data/lib/dry/validation/template.rb +0 -66
  83. data/lib/dry/validation/type_specs.rb +0 -70
  84. data/spec/extensions/monads/result_spec.rb +0 -40
  85. data/spec/extensions/struct/schema_spec.rb +0 -32
  86. data/spec/fixtures/locales/en.yml +0 -8
  87. data/spec/fixtures/locales/pl.yml +0 -22
  88. data/spec/integration/custom_error_messages_spec.rb +0 -54
  89. data/spec/integration/custom_predicates_spec.rb +0 -228
  90. data/spec/integration/hints_spec.rb +0 -170
  91. data/spec/integration/injecting_rules_spec.rb +0 -30
  92. data/spec/integration/json/defining_base_schema_spec.rb +0 -41
  93. data/spec/integration/localized_error_messages_spec.rb +0 -72
  94. data/spec/integration/message_compiler_spec.rb +0 -405
  95. data/spec/integration/messages/i18n_spec.rb +0 -104
  96. data/spec/integration/optional_keys_spec.rb +0 -28
  97. data/spec/integration/params/predicates/array_spec.rb +0 -287
  98. data/spec/integration/params/predicates/empty_spec.rb +0 -263
  99. data/spec/integration/params/predicates/eql_spec.rb +0 -327
  100. data/spec/integration/params/predicates/even_spec.rb +0 -455
  101. data/spec/integration/params/predicates/excluded_from_spec.rb +0 -455
  102. data/spec/integration/params/predicates/excludes_spec.rb +0 -391
  103. data/spec/integration/params/predicates/false_spec.rb +0 -455
  104. data/spec/integration/params/predicates/filled_spec.rb +0 -467
  105. data/spec/integration/params/predicates/format_spec.rb +0 -454
  106. data/spec/integration/params/predicates/gt_spec.rb +0 -519
  107. data/spec/integration/params/predicates/gteq_spec.rb +0 -519
  108. data/spec/integration/params/predicates/included_in_spec.rb +0 -455
  109. data/spec/integration/params/predicates/includes_spec.rb +0 -391
  110. data/spec/integration/params/predicates/key_spec.rb +0 -67
  111. data/spec/integration/params/predicates/lt_spec.rb +0 -519
  112. data/spec/integration/params/predicates/lteq_spec.rb +0 -519
  113. data/spec/integration/params/predicates/max_size_spec.rb +0 -391
  114. data/spec/integration/params/predicates/min_size_spec.rb +0 -391
  115. data/spec/integration/params/predicates/none_spec.rb +0 -265
  116. data/spec/integration/params/predicates/not_eql_spec.rb +0 -327
  117. data/spec/integration/params/predicates/odd_spec.rb +0 -455
  118. data/spec/integration/params/predicates/size/fixed_spec.rb +0 -393
  119. data/spec/integration/params/predicates/size/range_spec.rb +0 -396
  120. data/spec/integration/params/predicates/true_spec.rb +0 -455
  121. data/spec/integration/params/predicates/type_spec.rb +0 -391
  122. data/spec/integration/result_spec.rb +0 -81
  123. data/spec/integration/schema/array_schema_spec.rb +0 -59
  124. data/spec/integration/schema/check_rules_spec.rb +0 -119
  125. data/spec/integration/schema/check_with_nested_el_spec.rb +0 -37
  126. data/spec/integration/schema/check_with_nth_el_spec.rb +0 -25
  127. data/spec/integration/schema/default_settings_spec.rb +0 -11
  128. data/spec/integration/schema/defining_base_schema_spec.rb +0 -41
  129. data/spec/integration/schema/dynamic_predicate_args_spec.rb +0 -43
  130. data/spec/integration/schema/each_with_set_spec.rb +0 -70
  131. data/spec/integration/schema/extending_dsl_spec.rb +0 -27
  132. data/spec/integration/schema/form_spec.rb +0 -236
  133. data/spec/integration/schema/hash_schema_spec.rb +0 -47
  134. data/spec/integration/schema/inheriting_schema_spec.rb +0 -31
  135. data/spec/integration/schema/input_processor_spec.rb +0 -46
  136. data/spec/integration/schema/json/explicit_types_spec.rb +0 -157
  137. data/spec/integration/schema/json_spec.rb +0 -163
  138. data/spec/integration/schema/macros/confirmation_spec.rb +0 -35
  139. data/spec/integration/schema/macros/each_spec.rb +0 -268
  140. data/spec/integration/schema/macros/filled_spec.rb +0 -87
  141. data/spec/integration/schema/macros/input_spec.rb +0 -139
  142. data/spec/integration/schema/macros/maybe_spec.rb +0 -99
  143. data/spec/integration/schema/macros/rule_spec.rb +0 -75
  144. data/spec/integration/schema/macros/value_spec.rb +0 -119
  145. data/spec/integration/schema/macros/when_spec.rb +0 -62
  146. data/spec/integration/schema/nested_schemas_spec.rb +0 -236
  147. data/spec/integration/schema/nested_values_spec.rb +0 -46
  148. data/spec/integration/schema/not_spec.rb +0 -34
  149. data/spec/integration/schema/numbers_spec.rb +0 -19
  150. data/spec/integration/schema/option_with_default_spec.rb +0 -64
  151. data/spec/integration/schema/or_spec.rb +0 -87
  152. data/spec/integration/schema/params/defining_base_schema_spec.rb +0 -41
  153. data/spec/integration/schema/params/explicit_types_spec.rb +0 -195
  154. data/spec/integration/schema/params_spec.rb +0 -234
  155. data/spec/integration/schema/predicate_verification_spec.rb +0 -9
  156. data/spec/integration/schema/predicates/array_spec.rb +0 -295
  157. data/spec/integration/schema/predicates/custom_spec.rb +0 -103
  158. data/spec/integration/schema/predicates/empty_spec.rb +0 -263
  159. data/spec/integration/schema/predicates/eql_spec.rb +0 -327
  160. data/spec/integration/schema/predicates/even_spec.rb +0 -455
  161. data/spec/integration/schema/predicates/excluded_from/array_spec.rb +0 -459
  162. data/spec/integration/schema/predicates/excluded_from/range_spec.rb +0 -459
  163. data/spec/integration/schema/predicates/excludes_spec.rb +0 -391
  164. data/spec/integration/schema/predicates/filled_spec.rb +0 -467
  165. data/spec/integration/schema/predicates/format_spec.rb +0 -455
  166. data/spec/integration/schema/predicates/gt_spec.rb +0 -519
  167. data/spec/integration/schema/predicates/gteq_spec.rb +0 -519
  168. data/spec/integration/schema/predicates/hash_spec.rb +0 -69
  169. data/spec/integration/schema/predicates/included_in/array_spec.rb +0 -459
  170. data/spec/integration/schema/predicates/included_in/range_spec.rb +0 -459
  171. data/spec/integration/schema/predicates/includes_spec.rb +0 -391
  172. data/spec/integration/schema/predicates/key_spec.rb +0 -88
  173. data/spec/integration/schema/predicates/lt_spec.rb +0 -520
  174. data/spec/integration/schema/predicates/lteq_spec.rb +0 -519
  175. data/spec/integration/schema/predicates/max_size_spec.rb +0 -391
  176. data/spec/integration/schema/predicates/min_size_spec.rb +0 -391
  177. data/spec/integration/schema/predicates/none_spec.rb +0 -265
  178. data/spec/integration/schema/predicates/not_eql_spec.rb +0 -391
  179. data/spec/integration/schema/predicates/odd_spec.rb +0 -455
  180. data/spec/integration/schema/predicates/size/fixed_spec.rb +0 -398
  181. data/spec/integration/schema/predicates/size/range_spec.rb +0 -395
  182. data/spec/integration/schema/predicates/type_spec.rb +0 -413
  183. data/spec/integration/schema/reusing_schema_spec.rb +0 -33
  184. data/spec/integration/schema/using_types_spec.rb +0 -135
  185. data/spec/integration/schema/validate_spec.rb +0 -120
  186. data/spec/integration/schema/xor_spec.rb +0 -35
  187. data/spec/integration/schema_builders_spec.rb +0 -17
  188. data/spec/integration/schema_spec.rb +0 -173
  189. data/spec/shared/message_compiler.rb +0 -11
  190. data/spec/shared/predicate_helper.rb +0 -15
  191. data/spec/shared/rule_compiler.rb +0 -8
  192. data/spec/spec_helper.rb +0 -62
  193. data/spec/support/define_struct.rb +0 -25
  194. data/spec/support/matchers.rb +0 -38
  195. data/spec/support/mutant.rb +0 -9
  196. data/spec/support/predicates_integration.rb +0 -7
  197. data/spec/unit/input_processor_compiler/json_spec.rb +0 -283
  198. data/spec/unit/input_processor_compiler/params_spec.rb +0 -328
  199. data/spec/unit/message_compiler/visit_failure_spec.rb +0 -38
  200. data/spec/unit/message_compiler/visit_spec.rb +0 -16
  201. data/spec/unit/message_compiler_spec.rb +0 -7
  202. data/spec/unit/predicate_registry_spec.rb +0 -34
  203. data/spec/unit/schema/key_spec.rb +0 -38
  204. data/spec/unit/schema/rule_spec.rb +0 -42
  205. data/spec/unit/schema/value_spec.rb +0 -131
  206. data/spec/unit/schema_spec.rb +0 -35
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/schema/config'
4
+ require 'dry/validation/macros'
5
+
6
+ module Dry
7
+ module Validation
8
+ # Configuration for contracts
9
+ #
10
+ # @see Contract#config
11
+ #
12
+ # @api public
13
+ class Config < Schema::Config
14
+ setting :macros, Macros::Container.new, &:dup
15
+
16
+ # @api private
17
+ def dup
18
+ config = super
19
+ config.macros = macros.dup
20
+ config
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+ require 'dry/core/constants'
5
+
6
+ module Dry
7
+ module Validation
8
+ include Dry::Core::Constants
9
+
10
+ DOT = '.'
11
+
12
+ # Root path is used for base errors in hash representation of error messages
13
+ ROOT_PATH = [nil].freeze
14
+
15
+ # Path to the default errors locale file
16
+ DEFAULT_ERRORS_NAMESPACE = 'dry_validation'
17
+
18
+ # Path to the default errors locale file
19
+ DEFAULT_ERRORS_PATH = Pathname(__FILE__).join('../../../../config/errors.yml').realpath.freeze
20
+
21
+ # Mapping for block kwarg options used by block_options
22
+ #
23
+ # @see Rule#block_options
24
+ BLOCK_OPTIONS_MAPPINGS = Hash.new { |_, key| key }.update(context: :_context).freeze
25
+
26
+ # Error raised when `rule` specifies one or more keys that the schema doesn't specify
27
+ InvalidKeysError = Class.new(StandardError)
28
+
29
+ # Error raised when a localized message was not found
30
+ MissingMessageError = Class.new(StandardError)
31
+
32
+ # Error raised when trying to define a schema in a contract class that already has a schema
33
+ DuplicateSchemaError = Class.new(StandardError)
34
+
35
+ # Error raised during initialization of a contract that has no schema defined
36
+ SchemaMissingError = Class.new(StandardError) do
37
+ # @api private
38
+ def initialize(klass)
39
+ super("#{klass} cannot be instantiated without a schema defined")
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'concurrent/map'
4
+
5
+ require 'dry/equalizer'
6
+ require 'dry/initializer'
7
+ require 'dry/schema/path'
8
+
9
+ require 'dry/validation/config'
10
+ require 'dry/validation/constants'
11
+ require 'dry/validation/rule'
12
+ require 'dry/validation/evaluator'
13
+ require 'dry/validation/messages/resolver'
14
+ require 'dry/validation/result'
15
+ require 'dry/validation/contract/class_interface'
16
+
17
+ module Dry
18
+ module Validation
19
+ # Contract objects apply rules to input
20
+ #
21
+ # A contract consists of a schema and rules. The schema is applied to the
22
+ # input before rules are applied, this way you can be sure that your rules
23
+ # won't be applied to values that didn't pass schema checks.
24
+ #
25
+ # It's up to you how exactly you're going to separate schema checks from
26
+ # your rules.
27
+ #
28
+ # @example
29
+ # class NewUserContract < Dry::Validation::Contract
30
+ # params do
31
+ # required(:email).filled(:string)
32
+ # required(:age).filled(:integer)
33
+ # optional(:login).maybe(:string, :filled?)
34
+ # optional(:password).maybe(:string, min_size?: 10)
35
+ # optional(:password_confirmation).maybe(:string)
36
+ # end
37
+ #
38
+ # rule(:password) do
39
+ # key.failure('is required') if values[:login] && !values[:password]
40
+ # end
41
+ #
42
+ # rule(:age) do
43
+ # key.failure('must be greater or equal 18') if values[:age] < 18
44
+ # end
45
+ # end
46
+ #
47
+ # new_user_contract = NewUserContract.new
48
+ # new_user_contract.call(email: 'jane@doe.org', age: 21)
49
+ #
50
+ # @api public
51
+ class Contract
52
+ include Dry::Equalizer(:schema, :rules, :messages, inspect: false)
53
+
54
+ extend Dry::Initializer
55
+ extend ClassInterface
56
+
57
+ config.messages.top_namespace = DEFAULT_ERRORS_NAMESPACE
58
+ config.messages.load_paths << DEFAULT_ERRORS_PATH
59
+
60
+ # @!attribute [r] config
61
+ # @return [Config] Contract's configuration object
62
+ # @api public
63
+ option :config, default: -> { self.class.config }
64
+
65
+ # @!attribute [r] macros
66
+ # @return [Macros::Container] Configured macros
67
+ # @see Macros::Container#register
68
+ # @api public
69
+ option :macros, default: -> { config.macros }
70
+
71
+ # @!attribute [r] schema
72
+ # @return [Dry::Schema::Params, Dry::Schema::JSON, Dry::Schema::Processor]
73
+ # @api private
74
+ option :schema, default: -> { self.class.__schema__ || raise(SchemaMissingError, self.class) }
75
+
76
+ # @!attribute [r] rules
77
+ # @return [Hash]
78
+ # @api private
79
+ option :rules, default: -> { self.class.rules }
80
+
81
+ # @!attribute [r] message_resolver
82
+ # @return [Messages::Resolver]
83
+ # @api private
84
+ option :message_resolver, default: -> { Messages::Resolver.new(messages) }
85
+
86
+ # Apply the contract to an input
87
+ #
88
+ # @param [Hash] input The input to validate
89
+ #
90
+ # @return [Result]
91
+ #
92
+ # @api public
93
+ def call(input)
94
+ Result.new(schema.(input), Concurrent::Map.new) do |result|
95
+ rules.each do |rule|
96
+ next if rule.keys.any? { |key| error?(result, key) }
97
+
98
+ rule_result = rule.(self, result)
99
+
100
+ rule_result.failures.each do |failure|
101
+ result.add_error(message_resolver[failure])
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ # Return a nice string representation
108
+ #
109
+ # @return [String]
110
+ #
111
+ # @api public
112
+ def inspect
113
+ %(#<#{self.class} schema=#{schema.inspect} rules=#{rules.inspect}>)
114
+ end
115
+
116
+ private
117
+
118
+ # @api private
119
+ def error?(result, spec)
120
+ path = Schema::Path[spec]
121
+
122
+ if path.multi_value?
123
+ return path.expand.any? { |nested_path| error?(result, nested_path) }
124
+ end
125
+
126
+ return true if result.error?(path)
127
+
128
+ path
129
+ .to_a[0..-2]
130
+ .any? { |key|
131
+ curr_path = Schema::Path[path.keys[0..path.keys.index(key)]]
132
+
133
+ return false unless result.error?(curr_path)
134
+
135
+ result.errors.any? { |err|
136
+ (other = Schema::Path[err.path]).same_root?(curr_path) && other == curr_path
137
+ }
138
+ }
139
+ end
140
+
141
+ # Get a registered macro
142
+ #
143
+ # @return [Proc,#to_proc]
144
+ #
145
+ # @api private
146
+ def macro(name, *args)
147
+ (macros.key?(name) ? macros[name] : Macros[name]).with(args)
148
+ end
149
+
150
+ # Return configured messages backend
151
+ #
152
+ # @return [Dry::Schema::Messages::YAML, Dry::Schema::Messages::I18n]
153
+ #
154
+ # @api private
155
+ def messages
156
+ self.class.messages
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,223 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/schema'
4
+ require 'dry/schema/messages'
5
+ require 'dry/schema/path'
6
+ require 'dry/schema/key_map'
7
+
8
+ require 'dry/validation/constants'
9
+ require 'dry/validation/macros'
10
+ require 'dry/validation/schema_ext'
11
+
12
+ module Dry
13
+ module Validation
14
+ class Contract
15
+ # Contract's class interface
16
+ #
17
+ # @see Contract
18
+ #
19
+ # @api public
20
+ module ClassInterface
21
+ include Macros::Registrar
22
+
23
+ # @api private
24
+ def inherited(klass)
25
+ super
26
+ klass.instance_variable_set('@config', config.dup)
27
+ end
28
+
29
+ # Configuration
30
+ #
31
+ # @example
32
+ # class MyContract < Dry::Validation::Contract
33
+ # config.messages.backend = :i18n
34
+ # end
35
+ #
36
+ # @return [Config]
37
+ #
38
+ # @api public
39
+ def config
40
+ @config ||= Validation::Config.new
41
+ end
42
+
43
+ # Return macros registered for this class
44
+ #
45
+ # @return [Macros::Container]
46
+ #
47
+ # @api public
48
+ def macros
49
+ config.macros
50
+ end
51
+
52
+ # Define a params schema for your contract
53
+ #
54
+ # This type of schema is suitable for HTTP parameters
55
+ #
56
+ # @return [Dry::Schema::Params,NilClass]
57
+ # @see https://dry-rb.org/gems/dry-schema/params/
58
+ #
59
+ # @api public
60
+ def params(external_schema = nil, &block)
61
+ define(:Params, external_schema, &block)
62
+ end
63
+
64
+ # Define a JSON schema for your contract
65
+ #
66
+ # This type of schema is suitable for JSON data
67
+ #
68
+ # @return [Dry::Schema::JSON,NilClass]
69
+ # @see https://dry-rb.org/gems/dry-schema/json/
70
+ #
71
+ # @api public
72
+ def json(external_schema = nil, &block)
73
+ define(:JSON, external_schema, &block)
74
+ end
75
+
76
+ # Define a plain schema for your contract
77
+ #
78
+ # This type of schema does not offer coercion out of the box
79
+ #
80
+ # @return [Dry::Schema::Processor,NilClass]
81
+ # @see https://dry-rb.org/gems/dry-schema/
82
+ #
83
+ # @api public
84
+ def schema(external_schema = nil, &block)
85
+ define(:schema, external_schema, &block)
86
+ end
87
+
88
+ # Define a rule for your contract
89
+ #
90
+ # @example using a symbol
91
+ # rule(:age) do
92
+ # failure('must be at least 18') if values[:age] < 18
93
+ # end
94
+ #
95
+ # @example using a path to a value and a custom predicate
96
+ # rule('address.street') do
97
+ # failure('please provide a valid street address') if valid_street?(values[:street])
98
+ # end
99
+ #
100
+ # @return [Rule]
101
+ #
102
+ # @api public
103
+ def rule(*keys, &block)
104
+ ensure_valid_keys(*keys) if __schema__
105
+
106
+ Rule.new(keys: keys, block: block).tap do |rule|
107
+ rules << rule
108
+ end
109
+ end
110
+
111
+ # A shortcut that can be used to define contracts that won't be reused or inherited
112
+ #
113
+ # @example
114
+ # my_contract = Dry::Validation::Contract.build do
115
+ # params do
116
+ # required(:name).filled(:string)
117
+ # end
118
+ # end
119
+ #
120
+ # my_contract.call(name: "Jane")
121
+ #
122
+ # @return [Contract]
123
+ #
124
+ # @api public
125
+ def build(options = EMPTY_HASH, &block)
126
+ Class.new(self, &block).new(options)
127
+ end
128
+
129
+ # @api private
130
+ def __schema__
131
+ @__schema__ if defined?(@__schema__)
132
+ end
133
+
134
+ # Return rules defined in this class
135
+ #
136
+ # @return [Array<Rule>]
137
+ #
138
+ # @api private
139
+ def rules
140
+ @rules ||= EMPTY_ARRAY
141
+ .dup
142
+ .concat(superclass.respond_to?(:rules) ? superclass.rules : EMPTY_ARRAY)
143
+ end
144
+
145
+ # Return messages configured for this class
146
+ #
147
+ # @return [Dry::Schema::Messages]
148
+ #
149
+ # @api private
150
+ def messages
151
+ @messages ||= Schema::Messages.setup(config.messages)
152
+ end
153
+
154
+ private
155
+
156
+ # @api private
157
+ # rubocop:disable Metrics/AbcSize
158
+ def ensure_valid_keys(*keys)
159
+ valid_paths = key_map.to_dot_notation.map { |value| Schema::Path[value] }
160
+
161
+ invalid_keys = keys
162
+ .map { |key|
163
+ [key, Schema::Path[key]]
164
+ }
165
+ .map { |(key, path)|
166
+ if (last = path.last).is_a?(Array)
167
+ last.map { |last_key|
168
+ path_key = [*path.to_a[0..-2], last_key]
169
+ [path_key, Schema::Path[path_key]]
170
+ }
171
+ else
172
+ [[key, path]]
173
+ end
174
+ }
175
+ .flatten(1)
176
+ .reject { |(_, path)|
177
+ valid_paths.any? { |valid_path| valid_path.include?(path) }
178
+ }
179
+ .map(&:first)
180
+
181
+ return if invalid_keys.empty?
182
+
183
+ raise InvalidKeysError, <<~STR.strip
184
+ #{name}.rule specifies keys that are not defined by the schema: #{invalid_keys.inspect}
185
+ STR
186
+ end
187
+ # rubocop:enable Metrics/AbcSize
188
+
189
+ # @api private
190
+ def key_map
191
+ __schema__.key_map
192
+ end
193
+
194
+ # @api private
195
+ def core_schema_opts
196
+ { parent: superclass&.__schema__, config: config }
197
+ end
198
+
199
+ # @api private
200
+ def define(method_name, external_schema, &block)
201
+ return __schema__ if external_schema.nil? && block.nil?
202
+
203
+ unless __schema__.nil?
204
+ raise ::Dry::Validation::DuplicateSchemaError, 'Schema has already been defined'
205
+ end
206
+
207
+ schema_opts = core_schema_opts
208
+
209
+ schema_opts.update(parent: external_schema) if external_schema
210
+
211
+ case method_name
212
+ when :schema
213
+ @__schema__ = Schema.define(schema_opts, &block)
214
+ when :Params
215
+ @__schema__ = Schema.Params(schema_opts, &block)
216
+ when :JSON
217
+ @__schema__ = Schema.JSON(schema_opts, &block)
218
+ end
219
+ end
220
+ end
221
+ end
222
+ end
223
+ end