dry-validation 0.13.3 → 1.7.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 +4 -4
- data/CHANGELOG.md +506 -95
- data/LICENSE +1 -1
- data/README.md +16 -12
- data/config/errors.yml +3 -88
- data/dry-validation.gemspec +37 -24
- data/lib/dry/validation/config.rb +24 -0
- data/lib/dry/validation/constants.rb +43 -0
- data/lib/dry/validation/contract/class_interface.rb +230 -0
- data/lib/dry/validation/contract.rb +171 -0
- data/lib/dry/validation/evaluator.rb +224 -0
- data/lib/dry/validation/extensions/hints.rb +67 -0
- data/lib/dry/validation/extensions/monads.rb +24 -8
- data/lib/dry/validation/extensions/predicates_as_macros.rb +75 -0
- data/lib/dry/validation/failures.rb +70 -0
- data/lib/dry/validation/function.rb +44 -0
- data/lib/dry/validation/macro.rb +38 -0
- data/lib/dry/validation/macros.rb +104 -0
- data/lib/dry/validation/message.rb +80 -80
- data/lib/dry/validation/message_set.rb +80 -105
- data/lib/dry/validation/messages/resolver.rb +131 -0
- data/lib/dry/validation/result.rb +183 -41
- data/lib/dry/validation/rule.rb +135 -0
- data/lib/dry/validation/schema_ext.rb +19 -0
- data/lib/dry/validation/values.rb +104 -0
- data/lib/dry/validation/version.rb +3 -1
- data/lib/dry/validation.rb +45 -28
- data/lib/dry-validation.rb +3 -1
- metadata +46 -344
- data/.codeclimate.yml +0 -17
- data/.gitignore +0 -9
- data/.rspec +0 -3
- data/.travis.yml +0 -29
- data/CONTRIBUTING.md +0 -31
- data/Gemfile +0 -25
- data/Rakefile +0 -22
- data/benchmarks/benchmark_form_invalid.rb +0 -64
- data/benchmarks/benchmark_form_valid.rb +0 -64
- data/benchmarks/benchmark_schema_invalid_huge.rb +0 -52
- data/benchmarks/profile_schema_call_invalid.rb +0 -20
- data/benchmarks/profile_schema_call_valid.rb +0 -20
- data/benchmarks/profile_schema_definition.rb +0 -14
- data/benchmarks/profile_schema_huge_invalid.rb +0 -30
- data/benchmarks/profile_schema_messages_invalid.rb +0 -20
- data/benchmarks/suite.rb +0 -5
- data/examples/basic.rb +0 -15
- data/examples/each.rb +0 -14
- data/examples/json.rb +0 -12
- data/examples/multiple.rb +0 -27
- data/examples/nested.rb +0 -22
- data/examples/params.rb +0 -11
- data/lib/dry/validation/compat/form.rb +0 -67
- data/lib/dry/validation/deprecations.rb +0 -24
- data/lib/dry/validation/executor.rb +0 -91
- data/lib/dry/validation/extensions/struct.rb +0 -32
- data/lib/dry/validation/extensions.rb +0 -7
- data/lib/dry/validation/input_processor_compiler/json.rb +0 -45
- data/lib/dry/validation/input_processor_compiler/params.rb +0 -49
- data/lib/dry/validation/input_processor_compiler/sanitizer.rb +0 -47
- data/lib/dry/validation/input_processor_compiler.rb +0 -137
- data/lib/dry/validation/message_compiler/visitor_opts.rb +0 -37
- data/lib/dry/validation/message_compiler.rb +0 -188
- data/lib/dry/validation/messages/abstract.rb +0 -119
- data/lib/dry/validation/messages/i18n.rb +0 -47
- data/lib/dry/validation/messages/namespaced.rb +0 -39
- data/lib/dry/validation/messages/yaml.rb +0 -61
- data/lib/dry/validation/messages.rb +0 -14
- data/lib/dry/validation/predicate_registry.rb +0 -115
- data/lib/dry/validation/predicates.rb +0 -19
- data/lib/dry/validation/schema/check.rb +0 -37
- data/lib/dry/validation/schema/class_interface.rb +0 -190
- data/lib/dry/validation/schema/deprecated.rb +0 -30
- data/lib/dry/validation/schema/dsl.rb +0 -118
- data/lib/dry/validation/schema/form.rb +0 -9
- data/lib/dry/validation/schema/json.rb +0 -21
- data/lib/dry/validation/schema/key.rb +0 -71
- data/lib/dry/validation/schema/params.rb +0 -22
- data/lib/dry/validation/schema/rule.rb +0 -202
- data/lib/dry/validation/schema/value.rb +0 -211
- data/lib/dry/validation/schema.rb +0 -126
- data/lib/dry/validation/schema_compiler.rb +0 -81
- data/lib/dry/validation/template.rb +0 -66
- data/lib/dry/validation/type_specs.rb +0 -70
- data/log/.gitkeep +0 -0
- data/spec/extensions/monads/result_spec.rb +0 -40
- data/spec/extensions/struct/schema_spec.rb +0 -32
- data/spec/fixtures/locales/en.yml +0 -8
- data/spec/fixtures/locales/pl.yml +0 -22
- data/spec/integration/custom_error_messages_spec.rb +0 -54
- data/spec/integration/custom_predicates_spec.rb +0 -228
- data/spec/integration/hints_spec.rb +0 -170
- data/spec/integration/injecting_rules_spec.rb +0 -30
- data/spec/integration/json/defining_base_schema_spec.rb +0 -41
- data/spec/integration/localized_error_messages_spec.rb +0 -72
- data/spec/integration/message_compiler_spec.rb +0 -405
- data/spec/integration/messages/i18n_spec.rb +0 -104
- data/spec/integration/optional_keys_spec.rb +0 -28
- data/spec/integration/params/predicates/array_spec.rb +0 -287
- data/spec/integration/params/predicates/empty_spec.rb +0 -263
- data/spec/integration/params/predicates/eql_spec.rb +0 -327
- data/spec/integration/params/predicates/even_spec.rb +0 -455
- data/spec/integration/params/predicates/excluded_from_spec.rb +0 -455
- data/spec/integration/params/predicates/excludes_spec.rb +0 -391
- data/spec/integration/params/predicates/false_spec.rb +0 -455
- data/spec/integration/params/predicates/filled_spec.rb +0 -467
- data/spec/integration/params/predicates/format_spec.rb +0 -454
- data/spec/integration/params/predicates/gt_spec.rb +0 -519
- data/spec/integration/params/predicates/gteq_spec.rb +0 -519
- data/spec/integration/params/predicates/included_in_spec.rb +0 -455
- data/spec/integration/params/predicates/includes_spec.rb +0 -391
- data/spec/integration/params/predicates/key_spec.rb +0 -67
- data/spec/integration/params/predicates/lt_spec.rb +0 -519
- data/spec/integration/params/predicates/lteq_spec.rb +0 -519
- data/spec/integration/params/predicates/max_size_spec.rb +0 -391
- data/spec/integration/params/predicates/min_size_spec.rb +0 -391
- data/spec/integration/params/predicates/none_spec.rb +0 -265
- data/spec/integration/params/predicates/not_eql_spec.rb +0 -327
- data/spec/integration/params/predicates/odd_spec.rb +0 -455
- data/spec/integration/params/predicates/size/fixed_spec.rb +0 -393
- data/spec/integration/params/predicates/size/range_spec.rb +0 -396
- data/spec/integration/params/predicates/true_spec.rb +0 -455
- data/spec/integration/params/predicates/type_spec.rb +0 -391
- data/spec/integration/result_spec.rb +0 -81
- data/spec/integration/schema/array_schema_spec.rb +0 -59
- data/spec/integration/schema/check_rules_spec.rb +0 -119
- data/spec/integration/schema/check_with_nested_el_spec.rb +0 -37
- data/spec/integration/schema/check_with_nth_el_spec.rb +0 -25
- data/spec/integration/schema/default_settings_spec.rb +0 -11
- data/spec/integration/schema/defining_base_schema_spec.rb +0 -41
- data/spec/integration/schema/dynamic_predicate_args_spec.rb +0 -43
- data/spec/integration/schema/each_with_set_spec.rb +0 -70
- data/spec/integration/schema/extending_dsl_spec.rb +0 -27
- data/spec/integration/schema/form_spec.rb +0 -236
- data/spec/integration/schema/hash_schema_spec.rb +0 -47
- data/spec/integration/schema/inheriting_schema_spec.rb +0 -31
- data/spec/integration/schema/input_processor_spec.rb +0 -46
- data/spec/integration/schema/json/explicit_types_spec.rb +0 -157
- data/spec/integration/schema/json_spec.rb +0 -163
- data/spec/integration/schema/macros/confirmation_spec.rb +0 -35
- data/spec/integration/schema/macros/each_spec.rb +0 -268
- data/spec/integration/schema/macros/filled_spec.rb +0 -87
- data/spec/integration/schema/macros/input_spec.rb +0 -139
- data/spec/integration/schema/macros/maybe_spec.rb +0 -99
- data/spec/integration/schema/macros/rule_spec.rb +0 -75
- data/spec/integration/schema/macros/value_spec.rb +0 -119
- data/spec/integration/schema/macros/when_spec.rb +0 -62
- data/spec/integration/schema/nested_schemas_spec.rb +0 -236
- data/spec/integration/schema/nested_values_spec.rb +0 -46
- data/spec/integration/schema/not_spec.rb +0 -34
- data/spec/integration/schema/numbers_spec.rb +0 -19
- data/spec/integration/schema/option_with_default_spec.rb +0 -64
- data/spec/integration/schema/or_spec.rb +0 -87
- data/spec/integration/schema/params/defining_base_schema_spec.rb +0 -41
- data/spec/integration/schema/params/explicit_types_spec.rb +0 -195
- data/spec/integration/schema/params_spec.rb +0 -234
- data/spec/integration/schema/predicate_verification_spec.rb +0 -9
- data/spec/integration/schema/predicates/array_spec.rb +0 -295
- data/spec/integration/schema/predicates/custom_spec.rb +0 -103
- data/spec/integration/schema/predicates/empty_spec.rb +0 -263
- data/spec/integration/schema/predicates/eql_spec.rb +0 -327
- data/spec/integration/schema/predicates/even_spec.rb +0 -455
- data/spec/integration/schema/predicates/excluded_from/array_spec.rb +0 -459
- data/spec/integration/schema/predicates/excluded_from/range_spec.rb +0 -459
- data/spec/integration/schema/predicates/excludes_spec.rb +0 -391
- data/spec/integration/schema/predicates/filled_spec.rb +0 -467
- data/spec/integration/schema/predicates/format_spec.rb +0 -455
- data/spec/integration/schema/predicates/gt_spec.rb +0 -519
- data/spec/integration/schema/predicates/gteq_spec.rb +0 -519
- data/spec/integration/schema/predicates/hash_spec.rb +0 -69
- data/spec/integration/schema/predicates/included_in/array_spec.rb +0 -459
- data/spec/integration/schema/predicates/included_in/range_spec.rb +0 -459
- data/spec/integration/schema/predicates/includes_spec.rb +0 -391
- data/spec/integration/schema/predicates/key_spec.rb +0 -88
- data/spec/integration/schema/predicates/lt_spec.rb +0 -520
- data/spec/integration/schema/predicates/lteq_spec.rb +0 -519
- data/spec/integration/schema/predicates/max_size_spec.rb +0 -391
- data/spec/integration/schema/predicates/min_size_spec.rb +0 -391
- data/spec/integration/schema/predicates/none_spec.rb +0 -265
- data/spec/integration/schema/predicates/not_eql_spec.rb +0 -391
- data/spec/integration/schema/predicates/odd_spec.rb +0 -455
- data/spec/integration/schema/predicates/size/fixed_spec.rb +0 -398
- data/spec/integration/schema/predicates/size/range_spec.rb +0 -395
- data/spec/integration/schema/predicates/type_spec.rb +0 -413
- data/spec/integration/schema/reusing_schema_spec.rb +0 -33
- data/spec/integration/schema/using_types_spec.rb +0 -135
- data/spec/integration/schema/validate_spec.rb +0 -120
- data/spec/integration/schema/xor_spec.rb +0 -35
- data/spec/integration/schema_builders_spec.rb +0 -17
- data/spec/integration/schema_spec.rb +0 -173
- data/spec/shared/message_compiler.rb +0 -11
- data/spec/shared/predicate_helper.rb +0 -15
- data/spec/shared/rule_compiler.rb +0 -8
- data/spec/spec_helper.rb +0 -62
- data/spec/support/define_struct.rb +0 -25
- data/spec/support/matchers.rb +0 -38
- data/spec/support/mutant.rb +0 -9
- data/spec/support/predicates_integration.rb +0 -7
- data/spec/unit/input_processor_compiler/json_spec.rb +0 -283
- data/spec/unit/input_processor_compiler/params_spec.rb +0 -328
- data/spec/unit/message_compiler/visit_failure_spec.rb +0 -38
- data/spec/unit/message_compiler/visit_spec.rb +0 -16
- data/spec/unit/message_compiler_spec.rb +0 -7
- data/spec/unit/predicate_registry_spec.rb +0 -34
- data/spec/unit/schema/key_spec.rb +0 -38
- data/spec/unit/schema/rule_spec.rb +0 -42
- data/spec/unit/schema/value_spec.rb +0 -131
- data/spec/unit/schema_spec.rb +0 -35
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,25 +1,29 @@
|
|
1
|
+
<!--- this file is synced from dry-rb/template-gem project -->
|
1
2
|
[gem]: https://rubygems.org/gems/dry-validation
|
2
|
-
[
|
3
|
-
[
|
4
|
-
[
|
3
|
+
[actions]: https://github.com/dry-rb/dry-validation/actions
|
4
|
+
[codacy]: https://www.codacy.com/gh/dry-rb/dry-validation
|
5
|
+
[chat]: https://dry-rb.zulipchat.com
|
5
6
|
[inchpages]: http://inch-ci.org/github/dry-rb/dry-validation
|
6
7
|
|
7
|
-
# dry-validation [][chat]
|
8
9
|
|
9
10
|
[][gem]
|
10
|
-
[][actions]
|
12
|
+
[][codacy]
|
13
|
+
[][codacy]
|
13
14
|
[][inchpages]
|
14
15
|
|
15
|
-
##
|
16
|
+
## Links
|
16
17
|
|
17
|
-
|
18
|
+
* [User documentation](https://dry-rb.org/gems/dry-validation)
|
19
|
+
* [API documentation](http://rubydoc.info/gems/dry-validation)
|
18
20
|
|
19
|
-
##
|
21
|
+
## Supported Ruby versions
|
22
|
+
|
23
|
+
This library officially supports the following Ruby versions:
|
20
24
|
|
21
|
-
*
|
22
|
-
*
|
25
|
+
* MRI `>= 2.6.0`
|
26
|
+
* ~~jruby~~ `>= 9.3` (we are waiting for [2.6 support](https://github.com/jruby/jruby/issues/6161))
|
23
27
|
|
24
28
|
## License
|
25
29
|
|
data/config/errors.yml
CHANGED
@@ -1,89 +1,4 @@
|
|
1
1
|
en:
|
2
|
-
|
3
|
-
|
4
|
-
|
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
|
-
none?: "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}"
|
2
|
+
dry_validation:
|
3
|
+
errors:
|
4
|
+
acceptance: "must accept %{key}"
|
data/dry-validation.gemspec
CHANGED
@@ -1,28 +1,41 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# this file is synced from dry-rb/template-gem project
|
4
|
+
|
5
|
+
lib = File.expand_path("lib", __dir__)
|
6
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
7
|
+
require "dry/validation/version"
|
2
8
|
|
3
9
|
Gem::Specification.new do |spec|
|
4
|
-
spec.name =
|
5
|
-
spec.
|
6
|
-
spec.
|
7
|
-
spec.
|
8
|
-
spec.
|
9
|
-
|
10
|
-
spec.
|
11
|
-
|
12
|
-
spec.
|
10
|
+
spec.name = "dry-validation"
|
11
|
+
spec.authors = ["Piotr Solnica"]
|
12
|
+
spec.email = ["piotr.solnica@gmail.com"]
|
13
|
+
spec.license = "MIT"
|
14
|
+
spec.version = Dry::Validation::VERSION.dup
|
15
|
+
|
16
|
+
spec.summary = "Validation library"
|
17
|
+
spec.description = spec.summary
|
18
|
+
spec.homepage = "https://dry-rb.org/gems/dry-validation"
|
19
|
+
spec.files = Dir["CHANGELOG.md", "LICENSE", "README.md", "dry-validation.gemspec", "lib/**/*", "config/*.yml"]
|
20
|
+
spec.bindir = "bin"
|
13
21
|
spec.executables = []
|
14
|
-
spec.
|
15
|
-
|
16
|
-
spec.
|
17
|
-
|
18
|
-
spec.
|
19
|
-
spec.
|
20
|
-
|
21
|
-
spec.
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
spec.
|
26
|
-
spec.
|
27
|
-
spec.
|
22
|
+
spec.require_paths = ["lib"]
|
23
|
+
|
24
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
25
|
+
spec.metadata["changelog_uri"] = "https://github.com/dry-rb/dry-validation/blob/master/CHANGELOG.md"
|
26
|
+
spec.metadata["source_code_uri"] = "https://github.com/dry-rb/dry-validation"
|
27
|
+
spec.metadata["bug_tracker_uri"] = "https://github.com/dry-rb/dry-validation/issues"
|
28
|
+
|
29
|
+
spec.required_ruby_version = ">= 2.6.0"
|
30
|
+
|
31
|
+
# to update dependencies edit project.yml
|
32
|
+
spec.add_runtime_dependency "concurrent-ruby", "~> 1.0"
|
33
|
+
spec.add_runtime_dependency "dry-container", "~> 0.7", ">= 0.7.1"
|
34
|
+
spec.add_runtime_dependency "dry-core", "~> 0.5", ">= 0.5"
|
35
|
+
spec.add_runtime_dependency "dry-initializer", "~> 3.0"
|
36
|
+
spec.add_runtime_dependency "dry-schema", "~> 1.8", ">= 1.8.0"
|
37
|
+
|
38
|
+
spec.add_development_dependency "bundler"
|
39
|
+
spec.add_development_dependency "rake"
|
40
|
+
spec.add_development_dependency "rspec"
|
28
41
|
end
|
@@ -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, default: Macros::Container.new, constructor: :dup.to_proc
|
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,230 @@
|
|
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_schemas, &block)
|
61
|
+
define(:Params, external_schemas, &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_schemas, &block)
|
73
|
+
define(:JSON, external_schemas, &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_schemas, &block)
|
85
|
+
define(:schema, external_schemas, &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
|
+
def ensure_valid_keys(*keys)
|
158
|
+
valid_paths = key_map.to_dot_notation
|
159
|
+
key_paths = key_paths(keys)
|
160
|
+
|
161
|
+
invalid_keys = key_paths.map { |(key, path)|
|
162
|
+
unless valid_paths.any? { |vp| vp.include?(path) || vp.include?("#{path}[]") }
|
163
|
+
key
|
164
|
+
end
|
165
|
+
}.compact.uniq
|
166
|
+
|
167
|
+
return if invalid_keys.empty?
|
168
|
+
|
169
|
+
raise InvalidKeysError, <<~STR.strip
|
170
|
+
#{name}.rule specifies keys that are not defined by the schema: #{invalid_keys.inspect}
|
171
|
+
STR
|
172
|
+
end
|
173
|
+
|
174
|
+
# @api private
|
175
|
+
def key_paths(keys)
|
176
|
+
keys.map { |key|
|
177
|
+
case key
|
178
|
+
when Hash
|
179
|
+
path = Schema::Path[key]
|
180
|
+
if path.multi_value?
|
181
|
+
*head, tail = Array(path)
|
182
|
+
[key].product(
|
183
|
+
tail.map { |el| [*head, *el] }.map { |parts| parts.join(DOT) }
|
184
|
+
)
|
185
|
+
else
|
186
|
+
[[key, path.to_a.join(DOT)]]
|
187
|
+
end
|
188
|
+
when Array
|
189
|
+
[[key, Schema::Path[key].to_a.join(DOT)]]
|
190
|
+
else
|
191
|
+
[[key, key.to_s]]
|
192
|
+
end
|
193
|
+
}.flatten(1)
|
194
|
+
end
|
195
|
+
|
196
|
+
# @api private
|
197
|
+
def key_map
|
198
|
+
__schema__.key_map
|
199
|
+
end
|
200
|
+
|
201
|
+
# @api private
|
202
|
+
def core_schema_opts
|
203
|
+
{parent: superclass&.__schema__, config: config}
|
204
|
+
end
|
205
|
+
|
206
|
+
# @api private
|
207
|
+
def define(method_name, external_schemas, &block)
|
208
|
+
return __schema__ if external_schemas.empty? && block.nil?
|
209
|
+
|
210
|
+
unless __schema__.nil?
|
211
|
+
raise ::Dry::Validation::DuplicateSchemaError, "Schema has already been defined"
|
212
|
+
end
|
213
|
+
|
214
|
+
schema_opts = core_schema_opts
|
215
|
+
|
216
|
+
schema_opts.update(parent: external_schemas) if external_schemas.any?
|
217
|
+
|
218
|
+
case method_name
|
219
|
+
when :schema
|
220
|
+
@__schema__ = Schema.define(**schema_opts, &block)
|
221
|
+
when :Params
|
222
|
+
@__schema__ = Schema.Params(**schema_opts, &block)
|
223
|
+
when :JSON
|
224
|
+
@__schema__ = Schema.JSON(**schema_opts, &block)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "concurrent/map"
|
4
|
+
|
5
|
+
require "dry/core/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] default_context
|
72
|
+
# @return [Hash] Default context for rules
|
73
|
+
# @api public
|
74
|
+
option :default_context, default: -> { EMPTY_HASH }
|
75
|
+
|
76
|
+
# @!attribute [r] schema
|
77
|
+
# @return [Dry::Schema::Params, Dry::Schema::JSON, Dry::Schema::Processor]
|
78
|
+
# @api private
|
79
|
+
option :schema, default: -> { self.class.__schema__ || raise(SchemaMissingError, self.class) }
|
80
|
+
|
81
|
+
# @!attribute [r] rules
|
82
|
+
# @return [Hash]
|
83
|
+
# @api private
|
84
|
+
option :rules, default: -> { self.class.rules }
|
85
|
+
|
86
|
+
# @!attribute [r] message_resolver
|
87
|
+
# @return [Messages::Resolver]
|
88
|
+
# @api private
|
89
|
+
option :message_resolver, default: -> { Messages::Resolver.new(messages) }
|
90
|
+
|
91
|
+
# Apply the contract to an input
|
92
|
+
#
|
93
|
+
# @param [Hash] input The input to validate
|
94
|
+
# @param [Hash] context Initial context for rules
|
95
|
+
#
|
96
|
+
# @return [Result]
|
97
|
+
#
|
98
|
+
# @api public
|
99
|
+
def call(input, context = EMPTY_HASH)
|
100
|
+
context_map = Concurrent::Map.new.tap do |map|
|
101
|
+
default_context.each { |key, value| map[key] = value }
|
102
|
+
context.each { |key, value| map[key] = value }
|
103
|
+
end
|
104
|
+
|
105
|
+
Result.new(schema.(input), context_map) do |result|
|
106
|
+
rules.each do |rule|
|
107
|
+
next if rule.keys.any? { |key| error?(result, key) }
|
108
|
+
|
109
|
+
rule_result = rule.(self, result)
|
110
|
+
|
111
|
+
rule_result.failures.each do |failure|
|
112
|
+
result.add_error(message_resolver.(**failure))
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Return a nice string representation
|
119
|
+
#
|
120
|
+
# @return [String]
|
121
|
+
#
|
122
|
+
# @api public
|
123
|
+
def inspect
|
124
|
+
%(#<#{self.class} schema=#{schema.inspect} rules=#{rules.inspect}>)
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
# @api private
|
130
|
+
def error?(result, spec)
|
131
|
+
path = Schema::Path[spec]
|
132
|
+
|
133
|
+
if path.multi_value?
|
134
|
+
return path.expand.any? { |nested_path| error?(result, nested_path) }
|
135
|
+
end
|
136
|
+
|
137
|
+
return true if result.schema_error?(path)
|
138
|
+
|
139
|
+
path
|
140
|
+
.to_a[0..-2]
|
141
|
+
.any? { |key|
|
142
|
+
curr_path = Schema::Path[path.keys[0..path.keys.index(key)]]
|
143
|
+
|
144
|
+
return false unless result.schema_error?(curr_path)
|
145
|
+
|
146
|
+
result.errors.any? { |err|
|
147
|
+
(other = Schema::Path[err.path]).same_root?(curr_path) && other == curr_path
|
148
|
+
}
|
149
|
+
}
|
150
|
+
end
|
151
|
+
|
152
|
+
# Get a registered macro
|
153
|
+
#
|
154
|
+
# @return [Proc,#to_proc]
|
155
|
+
#
|
156
|
+
# @api private
|
157
|
+
def macro(name, *args)
|
158
|
+
(macros.key?(name) ? macros[name] : Macros[name]).with(args)
|
159
|
+
end
|
160
|
+
|
161
|
+
# Return configured messages backend
|
162
|
+
#
|
163
|
+
# @return [Dry::Schema::Messages::YAML, Dry::Schema::Messages::I18n]
|
164
|
+
#
|
165
|
+
# @api private
|
166
|
+
def messages
|
167
|
+
self.class.messages
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|