dry-schema 0.6.0 → 1.0.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 +13 -0
- data/lib/dry/schema.rb +36 -3
- data/lib/dry/schema/constants.rb +8 -0
- data/lib/dry/schema/dsl.rb +7 -13
- data/lib/dry/schema/extensions/hints.rb +3 -0
- data/lib/dry/schema/extensions/hints/message_compiler_methods.rb +5 -0
- data/lib/dry/schema/extensions/hints/message_set_methods.rb +19 -1
- data/lib/dry/schema/extensions/hints/result_methods.rb +9 -2
- data/lib/dry/schema/extensions/monads.rb +10 -0
- data/lib/dry/schema/json.rb +1 -1
- data/lib/dry/schema/key.rb +3 -9
- data/lib/dry/schema/key_coercer.rb +1 -1
- data/lib/dry/schema/key_map.rb +2 -3
- data/lib/dry/schema/macros/array.rb +1 -2
- data/lib/dry/schema/macros/dsl.rb +81 -8
- data/lib/dry/schema/macros/each.rb +1 -1
- data/lib/dry/schema/macros/filled.rb +2 -1
- data/lib/dry/schema/macros/hash.rb +1 -1
- data/lib/dry/schema/macros/key.rb +32 -13
- data/lib/dry/schema/macros/maybe.rb +1 -1
- data/lib/dry/schema/macros/optional.rb +1 -1
- data/lib/dry/schema/macros/required.rb +1 -1
- data/lib/dry/schema/macros/schema.rb +1 -1
- data/lib/dry/schema/macros/value.rb +1 -1
- data/lib/dry/schema/message.rb +30 -1
- data/lib/dry/schema/message/or.rb +2 -0
- data/lib/dry/schema/message_set.rb +47 -2
- data/lib/dry/schema/messages/i18n.rb +4 -1
- data/lib/dry/schema/messages/namespaced.rb +1 -0
- data/lib/dry/schema/messages/template.rb +3 -3
- data/lib/dry/schema/messages/yaml.rb +10 -2
- data/lib/dry/schema/namespaced_rule.rb +12 -0
- data/lib/dry/schema/params.rb +1 -1
- data/lib/dry/schema/path.rb +35 -7
- data/lib/dry/schema/predicate.rb +13 -0
- data/lib/dry/schema/predicate_inferrer.rb +6 -9
- data/lib/dry/schema/processor.rb +7 -6
- data/lib/dry/schema/result.rb +6 -3
- data/lib/dry/schema/value_coercer.rb +1 -1
- data/lib/dry/schema/version.rb +1 -1
- metadata +7 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 63100127599f36457192bd183784388f1ca2328c0bd91f4e4d2adb2498b57bb2
|
4
|
+
data.tar.gz: 82c6cad8a1bda4c89723a2ccccce7eab8bb56c9e90b0ce385cc4d632993be90c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dbef0b527a8e4742b3845d8bebf45b88d960fa969956599f6504ce785fb9d2b7461ad8cc94e2e4b238956dc79641007d54be3b435f73551f2219f01db8b3f60d
|
7
|
+
data.tar.gz: c01c98b4600c7fcec9efa3f38f0cd6a27b86191804a7502b80da81d564282cb581ac5bc69c5c006cebb4826d0f7d6653230612a80f530a301494d1fe5af01203
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
# 1.0.0 2019-05-03
|
2
|
+
|
3
|
+
### Changed
|
4
|
+
|
5
|
+
* [BREAKING] `Result#to_hash` was removed (solnic)
|
6
|
+
|
7
|
+
### Fixed
|
8
|
+
|
9
|
+
* Setting `:any` as the type spec no longer crashes (solnic)
|
10
|
+
* `Result#error?` handles paths to array elements correctly (solnic)
|
11
|
+
|
12
|
+
[Compare v0.6.0...v1.0.0](https://github.com/dry-rb/dry-schema/compare/v0.6.0...v1.0.0)
|
13
|
+
|
1
14
|
# 0.6.0 2019-04-24
|
2
15
|
|
3
16
|
### Changed
|
data/lib/dry/schema.rb
CHANGED
@@ -8,31 +8,64 @@ require 'dry/schema/params'
|
|
8
8
|
require 'dry/schema/json'
|
9
9
|
|
10
10
|
module Dry
|
11
|
+
# Main interface
|
12
|
+
#
|
13
|
+
# @api public
|
11
14
|
module Schema
|
12
15
|
extend Dry::Core::Extensions
|
13
16
|
|
14
17
|
# Define a schema
|
15
18
|
#
|
19
|
+
# @example
|
20
|
+
# Dry::Schema.define do
|
21
|
+
# required(:name).filled(:string)
|
22
|
+
# required(:age).value(:integer, gt?: 0)
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# @param [Hash] options
|
26
|
+
#
|
16
27
|
# @return [Processor]
|
17
28
|
#
|
29
|
+
# @see DSL.new
|
30
|
+
#
|
18
31
|
# @api public
|
19
32
|
def self.define(**options, &block)
|
20
33
|
DSL.new(options, &block).call
|
21
34
|
end
|
22
35
|
|
23
|
-
# Define a
|
36
|
+
# Define a schema suitable for HTTP params
|
37
|
+
#
|
38
|
+
# This schema type uses `Types::Params` for coercion by default
|
39
|
+
#
|
40
|
+
# @example
|
41
|
+
# Dry::Schema.Params do
|
42
|
+
# required(:name).filled(:string)
|
43
|
+
# required(:age).value(:integer, gt?: 0)
|
44
|
+
# end
|
24
45
|
#
|
25
46
|
# @return [Params]
|
26
47
|
#
|
48
|
+
# @see Schema#define
|
49
|
+
#
|
27
50
|
# @api public
|
28
51
|
def self.Params(**options, &block)
|
29
52
|
define(**options, processor_type: Params, &block)
|
30
53
|
end
|
31
54
|
singleton_class.send(:alias_method, :Form, :Params)
|
32
55
|
|
33
|
-
# Define a JSON
|
56
|
+
# Define a schema suitable for JSON data
|
57
|
+
#
|
58
|
+
# This schema type uses `Types::JSON` for coercion by default
|
59
|
+
#
|
60
|
+
# @example
|
61
|
+
# Dry::Schema.JSON do
|
62
|
+
# required(:name).filled(:string)
|
63
|
+
# required(:age).value(:integer, gt?: 0)
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
# @return [Params]
|
34
67
|
#
|
35
|
-
# @
|
68
|
+
# @see Schema#define
|
36
69
|
#
|
37
70
|
# @api public
|
38
71
|
def self.JSON(**options, &block)
|
data/lib/dry/schema/constants.rb
CHANGED
@@ -4,6 +4,9 @@ require 'pathname'
|
|
4
4
|
require 'dry/core/constants'
|
5
5
|
|
6
6
|
module Dry
|
7
|
+
# Common constants used across the library
|
8
|
+
#
|
9
|
+
# @api public
|
7
10
|
module Schema
|
8
11
|
include Core::Constants
|
9
12
|
|
@@ -11,11 +14,16 @@ module Dry
|
|
11
14
|
QUESTION_MARK = '?'
|
12
15
|
DOT = '.'
|
13
16
|
|
17
|
+
# Path to the default set of localized messages bundled within the gem
|
14
18
|
DEFAULT_MESSAGES_PATH = Pathname(__dir__).join('../../../config/errors.yml').realpath.freeze
|
19
|
+
|
20
|
+
# Default namespace used for localized messages in YAML files
|
15
21
|
DEFAULT_MESSAGES_ROOT = 'dry_schema'
|
16
22
|
|
23
|
+
# An error raised when DSL is used in an incorrect way
|
17
24
|
InvalidSchemaError = Class.new(StandardError)
|
18
25
|
|
26
|
+
# An error raised when a localized message cannot be found
|
19
27
|
MissingMessageError = Class.new(StandardError) do
|
20
28
|
# @api private
|
21
29
|
def initialize(path)
|
data/lib/dry/schema/dsl.rb
CHANGED
@@ -52,28 +52,22 @@ module Dry
|
|
52
52
|
|
53
53
|
include ::Dry::Equalizer(:options)
|
54
54
|
|
55
|
-
#
|
56
|
-
# @return [Compiler] The rule compiler object
|
55
|
+
# @return [Compiler] The rule compiler object
|
57
56
|
option :compiler, default: -> { Compiler.new }
|
58
57
|
|
59
|
-
#
|
60
|
-
# @return [Compiler] The type of the processor (Params, JSON, or a custom sub-class)
|
58
|
+
# @return [Compiler] The type of the processor (Params, JSON, or a custom sub-class)
|
61
59
|
option :processor_type, default: -> { Processor }
|
62
60
|
|
63
|
-
#
|
64
|
-
# @return [Array] An array with macros defined within the DSL
|
61
|
+
# @return [Array] An array with macros defined within the DSL
|
65
62
|
option :macros, default: -> { EMPTY_ARRAY.dup }
|
66
63
|
|
67
|
-
#
|
68
|
-
# @return [Compiler] A key=>type map defined within the DSL
|
64
|
+
# @return [Compiler] A key=>type map defined within the DSL
|
69
65
|
option :types, default: -> { EMPTY_HASH.dup }
|
70
66
|
|
71
|
-
#
|
72
|
-
# @return [DSL] An optional parent DSL object that will be used to merge keys and rules
|
67
|
+
# @return [DSL] An optional parent DSL object that will be used to merge keys and rules
|
73
68
|
option :parent, optional: true
|
74
69
|
|
75
|
-
#
|
76
|
-
# @return [Config] Configuration object exposed via `#configure` method
|
70
|
+
# @return [Config] Configuration object exposed via `#configure` method
|
77
71
|
option :config, optional: true, default: proc { parent ? parent.config.dup : Config.new }
|
78
72
|
|
79
73
|
# Build a new DSL object and evaluate provided block
|
@@ -249,7 +243,7 @@ module Dry
|
|
249
243
|
|
250
244
|
# Resolve type object from the provided spec
|
251
245
|
#
|
252
|
-
# @param [Symbol, Array<Symbol>, Dry::Types::Type]
|
246
|
+
# @param [Symbol, Array<Symbol>, Dry::Types::Type] spec
|
253
247
|
#
|
254
248
|
# @return [Dry::Types::Type]
|
255
249
|
#
|
@@ -17,6 +17,8 @@ module Dry
|
|
17
17
|
#
|
18
18
|
# @api public
|
19
19
|
class Message
|
20
|
+
# Hints extension for Or messages
|
21
|
+
#
|
20
22
|
# @see Message::Or
|
21
23
|
#
|
22
24
|
# @api public
|
@@ -43,6 +45,7 @@ module Dry
|
|
43
45
|
end
|
44
46
|
end
|
45
47
|
|
48
|
+
# Hints extensions
|
46
49
|
module Extensions
|
47
50
|
Compiler.prepend(Hints::CompilerMethods)
|
48
51
|
MessageCompiler.prepend(Hints::MessageCompilerMethods)
|
@@ -4,6 +4,9 @@ module Dry
|
|
4
4
|
module Schema
|
5
5
|
module Extensions
|
6
6
|
module Hints
|
7
|
+
# Adds support for processing [:hint, ...] nodes produced by dry-logic
|
8
|
+
#
|
9
|
+
# @api private
|
7
10
|
module MessageCompilerMethods
|
8
11
|
HINT_TYPE_EXCLUSION = %i[
|
9
12
|
key? nil? bool? str? int? float? decimal?
|
@@ -12,8 +15,10 @@ module Dry
|
|
12
15
|
|
13
16
|
HINT_OTHER_EXCLUSION = %i[format? filled?].freeze
|
14
17
|
|
18
|
+
# @api private
|
15
19
|
attr_reader :hints
|
16
20
|
|
21
|
+
# @api private
|
17
22
|
def initialize(*args)
|
18
23
|
super
|
19
24
|
@hints = @options.fetch(:hints, true)
|
@@ -4,8 +4,19 @@ module Dry
|
|
4
4
|
module Schema
|
5
5
|
module Extensions
|
6
6
|
module Hints
|
7
|
+
# Hint extensions for MessageSet
|
8
|
+
#
|
9
|
+
# @api public
|
7
10
|
module MessageSetMethods
|
8
|
-
|
11
|
+
# Filtered message hints from all messages
|
12
|
+
#
|
13
|
+
# @return [Array<Message::Hint>]
|
14
|
+
attr_reader :hints
|
15
|
+
|
16
|
+
# Configuration option to enable/disable showing errors
|
17
|
+
#
|
18
|
+
# @return [Boolean]
|
19
|
+
attr_reader :failures
|
9
20
|
|
10
21
|
# @api private
|
11
22
|
def initialize(messages, options = EMPTY_HASH)
|
@@ -14,6 +25,13 @@ module Dry
|
|
14
25
|
@failures = options.fetch(:failures, true)
|
15
26
|
end
|
16
27
|
|
28
|
+
# Dump message set to a hash with either all messages or just hints
|
29
|
+
#
|
30
|
+
# @see MessageSet#to_h
|
31
|
+
# @see ResultMethods#hints
|
32
|
+
#
|
33
|
+
# @return [Hash<Symbol=>Array<String>>]
|
34
|
+
#
|
17
35
|
# @api public
|
18
36
|
def to_h
|
19
37
|
@to_h ||= failures ? messages_map : messages_map(hints)
|
@@ -4,9 +4,16 @@ module Dry
|
|
4
4
|
module Schema
|
5
5
|
module Extensions
|
6
6
|
module Hints
|
7
|
+
# Get errors exclusively without hints
|
8
|
+
#
|
9
|
+
# @api public
|
7
10
|
module ResultMethods
|
11
|
+
# Return error messages exclusively
|
12
|
+
#
|
8
13
|
# @see Result#errors
|
9
14
|
#
|
15
|
+
# @return [MessageSet]
|
16
|
+
#
|
10
17
|
# @api public
|
11
18
|
def errors(options = EMPTY_HASH)
|
12
19
|
message_set(options.merge(hints: false))
|
@@ -16,7 +23,7 @@ module Dry
|
|
16
23
|
#
|
17
24
|
# @see #message_set
|
18
25
|
#
|
19
|
-
# @return [
|
26
|
+
# @return [MessageSet]
|
20
27
|
#
|
21
28
|
# @api public
|
22
29
|
def messages(options = EMPTY_HASH)
|
@@ -27,7 +34,7 @@ module Dry
|
|
27
34
|
#
|
28
35
|
# @see #message_set
|
29
36
|
#
|
30
|
-
# @return [
|
37
|
+
# @return [MessageSet]
|
31
38
|
#
|
32
39
|
# @api public
|
33
40
|
def hints(options = EMPTY_HASH)
|
@@ -4,9 +4,19 @@ require 'dry/monads/result'
|
|
4
4
|
|
5
5
|
module Dry
|
6
6
|
module Schema
|
7
|
+
# Monad extension for Result
|
8
|
+
#
|
9
|
+
# @api public
|
7
10
|
class Result
|
8
11
|
include Dry::Monads::Result::Mixin
|
9
12
|
|
13
|
+
# Turn result into a monad
|
14
|
+
#
|
15
|
+
# This makes result objects work with dry-monads (or anything with a compatible interface)
|
16
|
+
#
|
17
|
+
# @return [Dry::Monads::Success,Dry::Monads::Failure]
|
18
|
+
#
|
19
|
+
# @api public
|
10
20
|
def to_monad
|
11
21
|
if success?
|
12
22
|
Success(self)
|
data/lib/dry/schema/json.rb
CHANGED
data/lib/dry/schema/key.rb
CHANGED
@@ -12,19 +12,13 @@ module Dry
|
|
12
12
|
|
13
13
|
include Dry.Equalizer(:name, :coercer)
|
14
14
|
|
15
|
-
#
|
16
|
-
# @return [Symbol] The key identifier
|
17
|
-
# @api public
|
15
|
+
# @return [Symbol] The key identifier
|
18
16
|
attr_reader :id
|
19
17
|
|
20
|
-
#
|
21
|
-
# @return [Symbol, String, Object] The actual key name expected in an input hash
|
22
|
-
# @api public
|
18
|
+
# @return [Symbol, String, Object] The actual key name expected in an input hash
|
23
19
|
attr_reader :name
|
24
20
|
|
25
|
-
#
|
26
|
-
# @return [Proc, #call] A key name coercer function
|
27
|
-
# @api public
|
21
|
+
# @return [Proc, #call] A key name coercer function
|
28
22
|
attr_reader :coercer
|
29
23
|
|
30
24
|
# @api private
|
data/lib/dry/schema/key_map.rb
CHANGED
@@ -21,8 +21,7 @@ module Dry
|
|
21
21
|
include Dry.Equalizer(:keys)
|
22
22
|
include Enumerable
|
23
23
|
|
24
|
-
#
|
25
|
-
# @return [Array<Key>] A list of defined key objects
|
24
|
+
# @return [Array<Key>] A list of defined key objects
|
26
25
|
attr_reader :keys
|
27
26
|
|
28
27
|
# Coerce a list of key specs into a key map
|
@@ -41,7 +40,7 @@ module Dry
|
|
41
40
|
|
42
41
|
# Build new, or returned a cached instance of a key map
|
43
42
|
#
|
44
|
-
# @param [Array<Symbol
|
43
|
+
# @param [Array<Symbol>, Array, Hash<Symbol=>Array>] args
|
45
44
|
#
|
46
45
|
# @return [KeyMap]
|
47
46
|
def self.new(*args)
|
@@ -7,7 +7,7 @@ module Dry
|
|
7
7
|
module Macros
|
8
8
|
# Macro used to specify predicates for each element of an array
|
9
9
|
#
|
10
|
-
# @api
|
10
|
+
# @api private
|
11
11
|
class Array < DSL
|
12
12
|
# @api private
|
13
13
|
def value(*args, **opts, &block)
|
@@ -32,7 +32,6 @@ module Dry
|
|
32
32
|
def to_ast(*)
|
33
33
|
[:and, [trace.array?.to_ast, [:each, trace.to_ast]]]
|
34
34
|
end
|
35
|
-
|
36
35
|
alias_method :ast, :to_ast
|
37
36
|
end
|
38
37
|
end
|
@@ -16,15 +16,37 @@ module Dry
|
|
16
16
|
undef :eql?
|
17
17
|
undef :nil?
|
18
18
|
|
19
|
-
#
|
19
|
+
# @!attribute [r] chain
|
20
|
+
# Indicate if the macro should append its rules to the provided trace
|
21
|
+
# @return [Boolean]
|
22
|
+
# @api private
|
20
23
|
option :chain, default: -> { true }
|
21
24
|
|
22
25
|
# @!attribute [r] predicate_inferrer
|
26
|
+
# PredicateInferrer is used to infer predicate type-check from a type spec
|
23
27
|
# @return [PredicateInferrer]
|
24
28
|
# @api private
|
25
29
|
option :predicate_inferrer, default: proc { PredicateInferrer.new(compiler.predicates) }
|
26
30
|
|
27
|
-
#
|
31
|
+
# @overload value(*predicates, **predicate_opts)
|
32
|
+
# Set predicates without and with arguments
|
33
|
+
#
|
34
|
+
# @param [Array<Symbol>] predicates
|
35
|
+
# @param [Hash] predicate_opts
|
36
|
+
#
|
37
|
+
# @example with a predicate
|
38
|
+
# required(:name).value(:filled?)
|
39
|
+
#
|
40
|
+
# @example with a predicate with arguments
|
41
|
+
# required(:name).value(min_size?: 2)
|
42
|
+
#
|
43
|
+
# @example with a predicate with and without arguments
|
44
|
+
# required(:name).value(:filled?, min_size?: 2)
|
45
|
+
#
|
46
|
+
# @example with a block
|
47
|
+
# required(:name).value { filled? & min_size?(2) }
|
48
|
+
#
|
49
|
+
# @return [Macros::Core]
|
28
50
|
#
|
29
51
|
# @api public
|
30
52
|
def value(*predicates, **opts, &block)
|
@@ -35,6 +57,14 @@ module Dry
|
|
35
57
|
|
36
58
|
# Prepends `:filled?` predicate
|
37
59
|
#
|
60
|
+
# @example with a type spec
|
61
|
+
# required(:name).filled(:string)
|
62
|
+
#
|
63
|
+
# @example with a type spec and a predicate
|
64
|
+
# required(:name).filled(:string, format?: /\w+/)
|
65
|
+
#
|
66
|
+
# @return [Macros::Core]
|
67
|
+
#
|
38
68
|
# @api public
|
39
69
|
def filled(*args, &block)
|
40
70
|
append_macro(Macros::Filled) do |macro|
|
@@ -42,7 +72,18 @@ module Dry
|
|
42
72
|
end
|
43
73
|
end
|
44
74
|
|
45
|
-
# Specify a nested hash without enforced hash
|
75
|
+
# Specify a nested hash without enforced `hash?` type-check
|
76
|
+
#
|
77
|
+
# This is a simpler building block than `hash` macro, use it
|
78
|
+
# when you want to provide `hash?` type-check with other rules
|
79
|
+
# manually.
|
80
|
+
#
|
81
|
+
# @example
|
82
|
+
# required(:tags).value(:hash, min_size?: 1).schema do
|
83
|
+
# required(:name).value(:string)
|
84
|
+
# end
|
85
|
+
#
|
86
|
+
# @return [Macros::Core]
|
46
87
|
#
|
47
88
|
# @api public
|
48
89
|
def schema(*args, &block)
|
@@ -51,9 +92,12 @@ module Dry
|
|
51
92
|
end
|
52
93
|
end
|
53
94
|
|
54
|
-
# Specify a nested hash with enforced hash
|
95
|
+
# Specify a nested hash with enforced `hash?` type-check
|
55
96
|
#
|
56
|
-
# @
|
97
|
+
# @example
|
98
|
+
# required(:tags).hash do
|
99
|
+
# required(:name).value(:string)
|
100
|
+
# end
|
57
101
|
#
|
58
102
|
# @api public
|
59
103
|
def hash(*args, &block)
|
@@ -64,6 +108,20 @@ module Dry
|
|
64
108
|
|
65
109
|
# Specify predicates that should be applied to each element of an array
|
66
110
|
#
|
111
|
+
# This is a simpler building block than `array` macro, use it
|
112
|
+
# when you want to provide `array?` type-check with other rules
|
113
|
+
# manually.
|
114
|
+
#
|
115
|
+
# @example a list of strings
|
116
|
+
# required(:tags).value(:array, min_size?: 2).each(:str?)
|
117
|
+
#
|
118
|
+
# @example a list of hashes
|
119
|
+
# required(:tags).value(:array, min_size?: 2).each(:hash) do
|
120
|
+
# required(:name).filled(:string)
|
121
|
+
# end
|
122
|
+
#
|
123
|
+
# @return [Macros::Core]
|
124
|
+
#
|
67
125
|
# @api public
|
68
126
|
def each(*args, &block)
|
69
127
|
append_macro(Macros::Each) do |macro|
|
@@ -71,7 +129,17 @@ module Dry
|
|
71
129
|
end
|
72
130
|
end
|
73
131
|
|
74
|
-
# Like `each
|
132
|
+
# Like `each` but sets `array?` type-check
|
133
|
+
#
|
134
|
+
# @example a list of strings
|
135
|
+
# required(:tags).array(:str?)
|
136
|
+
#
|
137
|
+
# @example a list of hashes
|
138
|
+
# required(:tags).array(:hash) do
|
139
|
+
# required(:name).filled(:string)
|
140
|
+
# end
|
141
|
+
#
|
142
|
+
# @return [Macros::Core]
|
75
143
|
#
|
76
144
|
# @api public
|
77
145
|
def array(*args, &block)
|
@@ -82,7 +150,10 @@ module Dry
|
|
82
150
|
|
83
151
|
# Set type spec
|
84
152
|
#
|
85
|
-
# @
|
153
|
+
# @example
|
154
|
+
# required(:name).type(:string).value(min_size?: 2)
|
155
|
+
#
|
156
|
+
# @param [Symbol, Array, Dry::Types::Type] spec
|
86
157
|
#
|
87
158
|
# @return [Macros::Key]
|
88
159
|
#
|
@@ -129,13 +200,15 @@ module Dry
|
|
129
200
|
|
130
201
|
type_predicates = predicate_inferrer[resolved_type]
|
131
202
|
|
132
|
-
unless predicates.include?(type_predicates)
|
203
|
+
unless type_predicates.empty? || predicates.include?(type_predicates)
|
133
204
|
if type_predicates.is_a?(::Array) && type_predicates.size.equal?(1)
|
134
205
|
predicates.unshift(type_predicates[0])
|
135
206
|
else
|
136
207
|
predicates.unshift(type_predicates)
|
137
208
|
end
|
138
209
|
end
|
210
|
+
|
211
|
+
return self if predicates.empty?
|
139
212
|
end
|
140
213
|
|
141
214
|
yield(*predicates, type_spec: type_spec)
|
@@ -7,8 +7,9 @@ module Dry
|
|
7
7
|
module Macros
|
8
8
|
# Macro used to prepend `:filled?` predicate
|
9
9
|
#
|
10
|
-
# @api
|
10
|
+
# @api private
|
11
11
|
class Filled < Value
|
12
|
+
# @api private
|
12
13
|
def call(*predicates, **opts, &block)
|
13
14
|
if predicates.include?(:empty?)
|
14
15
|
raise ::Dry::Schema::InvalidSchemaError, 'Using filled with empty? predicate is invalid'
|
@@ -8,21 +8,20 @@ require 'dry/schema/constants'
|
|
8
8
|
module Dry
|
9
9
|
module Schema
|
10
10
|
module Macros
|
11
|
-
# Base macro for specifying rules applied to a value found under
|
12
|
-
#
|
13
|
-
# @see DSL#key
|
11
|
+
# Base macro for specifying rules applied to a value found under a key
|
14
12
|
#
|
15
13
|
# @api public
|
16
14
|
class Key < DSL
|
17
|
-
#
|
18
|
-
#
|
19
|
-
# @api private
|
15
|
+
# @return [Schema::DSL]
|
16
|
+
# @api private
|
20
17
|
option :filter_schema, optional: true, default: proc { schema_dsl&.new }
|
21
18
|
|
22
|
-
# Specify predicates that should be
|
23
|
-
# before coercion is applied
|
19
|
+
# Specify predicates that should be applied before coercion
|
24
20
|
#
|
25
|
-
# @
|
21
|
+
# @example check format before coercing to a date
|
22
|
+
# required(:publish_date).filter(format?: /\d{4}-\d{2}-\d{2}).value(:date)
|
23
|
+
#
|
24
|
+
# @see Macros::Key#value
|
26
25
|
#
|
27
26
|
# @return [Macros::Key]
|
28
27
|
#
|
@@ -32,12 +31,26 @@ module Dry
|
|
32
31
|
self
|
33
32
|
end
|
34
33
|
|
35
|
-
#
|
34
|
+
# @overload value(type_spec, *predicates, **predicate_opts)
|
35
|
+
# Set type specification and predicates
|
36
36
|
#
|
37
|
-
#
|
37
|
+
# @param [Symbol,Types::Type,Array] type_spec
|
38
|
+
# @param [Array<Symbol>] predicates
|
39
|
+
# @param [Hash] predicate_opts
|
40
|
+
#
|
41
|
+
# @example with a predicate
|
42
|
+
# required(:name).value(:string, :filled?)
|
43
|
+
#
|
44
|
+
# @example with a predicate with arguments
|
45
|
+
# required(:name).value(:string, min_size?: 2)
|
46
|
+
#
|
47
|
+
# @example with a block
|
48
|
+
# required(:name).value(:string) { filled? & min_size?(2) }
|
38
49
|
#
|
39
50
|
# @return [Macros::Key]
|
40
51
|
#
|
52
|
+
# @see Macros::DSL#value
|
53
|
+
#
|
41
54
|
# @api public
|
42
55
|
def value(*args, **opts, &block)
|
43
56
|
extract_type_spec(*args) do |*predicates, type_spec:|
|
@@ -47,7 +60,10 @@ module Dry
|
|
47
60
|
|
48
61
|
# Set type specification and predicates for a filled value
|
49
62
|
#
|
50
|
-
# @
|
63
|
+
# @example
|
64
|
+
# required(:name).filled(:string)
|
65
|
+
#
|
66
|
+
# @see Macros::Key#value
|
51
67
|
#
|
52
68
|
# @return [Macros::Key]
|
53
69
|
#
|
@@ -60,7 +76,10 @@ module Dry
|
|
60
76
|
|
61
77
|
# Set type specification and predicates for a maybe value
|
62
78
|
#
|
63
|
-
# @
|
79
|
+
# @example
|
80
|
+
# required(:name).maybe(:string)
|
81
|
+
#
|
82
|
+
# @see Macros::Key#value
|
64
83
|
#
|
65
84
|
# @return [Macros::Key]
|
66
85
|
#
|
data/lib/dry/schema/message.rb
CHANGED
@@ -16,16 +16,34 @@ module Dry
|
|
16
16
|
|
17
17
|
extend Dry::Initializer
|
18
18
|
|
19
|
+
# @!attribute [r] text
|
20
|
+
# Message text representation created from a localized template
|
21
|
+
# @return [String]
|
19
22
|
option :text
|
20
23
|
|
24
|
+
# @!attribute [r] path
|
25
|
+
# Path to the value
|
26
|
+
# @return [String]
|
21
27
|
option :path
|
22
28
|
|
29
|
+
# @!attribute [r] predicate
|
30
|
+
# Predicate identifier that was used to produce a message
|
31
|
+
# @return [Symbol]
|
23
32
|
option :predicate
|
24
33
|
|
34
|
+
# @!attribute [r] args
|
35
|
+
# Optional list of arguments used by the predicate
|
36
|
+
# @return [Array]
|
25
37
|
option :args, default: proc { EMPTY_ARRAY }
|
26
38
|
|
39
|
+
# @!attribute [r] input
|
40
|
+
# The input value
|
41
|
+
# @return [Object]
|
27
42
|
option :input
|
28
43
|
|
44
|
+
# @!attribute [r] meta
|
45
|
+
# Arbitrary meta data
|
46
|
+
# @return [Hash]
|
29
47
|
option :meta, optional: true, default: proc { EMPTY_HASH }
|
30
48
|
|
31
49
|
# Dump the message to a representation suitable for the message set hash
|
@@ -38,16 +56,27 @@ module Dry
|
|
38
56
|
end
|
39
57
|
alias to_s dump
|
40
58
|
|
59
|
+
# See if another message is the same
|
60
|
+
#
|
61
|
+
# If a string is passed, it will be compared with the text
|
62
|
+
#
|
63
|
+
# @param [Message,String]
|
64
|
+
#
|
65
|
+
# @return [Boolean]
|
66
|
+
#
|
41
67
|
# @api private
|
42
68
|
def eql?(other)
|
43
69
|
other.is_a?(String) ? text == other : super
|
44
70
|
end
|
45
71
|
|
72
|
+
# See which message is higher in the hierarchy
|
73
|
+
#
|
74
|
+
# @api private
|
46
75
|
def <=>(other)
|
47
76
|
l_path = Path[path]
|
48
77
|
r_path = Path[other.path]
|
49
78
|
|
50
|
-
unless l_path.
|
79
|
+
unless l_path.same_root?(r_path)
|
51
80
|
raise ArgumentError, 'Cannot compare messages from different root paths'
|
52
81
|
end
|
53
82
|
|
@@ -13,7 +13,21 @@ module Dry
|
|
13
13
|
include Enumerable
|
14
14
|
include Dry::Equalizer(:messages, :options)
|
15
15
|
|
16
|
-
|
16
|
+
# A list of compiled message objects
|
17
|
+
#
|
18
|
+
# @return [Array<Message>]
|
19
|
+
attr_reader :messages
|
20
|
+
|
21
|
+
# An internal hash that is filled in with dumped messages
|
22
|
+
# when a message set is coerced to a hash
|
23
|
+
#
|
24
|
+
# @return [Hash<Symbol=>[Array,Hash]>]
|
25
|
+
attr_reader :placeholders
|
26
|
+
|
27
|
+
# Options hash
|
28
|
+
#
|
29
|
+
# @return [Hash]
|
30
|
+
attr_reader :options
|
17
31
|
|
18
32
|
# @api private
|
19
33
|
def self.[](messages, options = EMPTY_HASH)
|
@@ -27,6 +41,15 @@ module Dry
|
|
27
41
|
initialize_placeholders!
|
28
42
|
end
|
29
43
|
|
44
|
+
# Iterate over messages
|
45
|
+
#
|
46
|
+
# @example
|
47
|
+
# result.errors.each do |message|
|
48
|
+
# puts message.text
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# @return [Array]
|
52
|
+
#
|
30
53
|
# @api public
|
31
54
|
def each(&block)
|
32
55
|
return self if empty?
|
@@ -35,23 +58,45 @@ module Dry
|
|
35
58
|
messages.each(&block)
|
36
59
|
end
|
37
60
|
|
61
|
+
# Dump message set to a hash
|
62
|
+
#
|
63
|
+
# @return [Hash<Symbol=>Array<String>>]
|
64
|
+
#
|
38
65
|
# @api public
|
39
66
|
def to_h
|
40
67
|
@to_h ||= messages_map
|
41
68
|
end
|
42
69
|
alias_method :to_hash, :to_h
|
43
70
|
|
71
|
+
# Get a list of message texts for the given key
|
72
|
+
#
|
73
|
+
# @param [Symbol] key
|
74
|
+
#
|
75
|
+
# @return [Array<String>]
|
76
|
+
#
|
44
77
|
# @api public
|
45
78
|
def [](key)
|
46
79
|
to_h[key]
|
47
80
|
end
|
48
81
|
|
82
|
+
# Get a list of message texts for the given key
|
83
|
+
#
|
84
|
+
# @param [Symbol] key
|
85
|
+
#
|
86
|
+
# @return [Array<String>]
|
87
|
+
#
|
88
|
+
# @raise KeyError
|
89
|
+
#
|
49
90
|
# @api public
|
50
91
|
def fetch(key)
|
51
92
|
self[key] || raise(KeyError, "+#{key}+ message was not found")
|
52
93
|
end
|
53
94
|
|
54
|
-
#
|
95
|
+
# Check if a message set is empty
|
96
|
+
#
|
97
|
+
# @return [Boolean]
|
98
|
+
#
|
99
|
+
# @api public
|
55
100
|
def empty?
|
56
101
|
@empty ||= messages.empty?
|
57
102
|
end
|
@@ -9,6 +9,9 @@ module Dry
|
|
9
9
|
#
|
10
10
|
# @api public
|
11
11
|
class Messages::I18n < Messages::Abstract
|
12
|
+
# Translation function
|
13
|
+
#
|
14
|
+
# @return [Method]
|
12
15
|
attr_reader :t
|
13
16
|
|
14
17
|
# @api private
|
@@ -82,7 +85,7 @@ module Dry
|
|
82
85
|
def store_translations(data)
|
83
86
|
locales = data.keys.map(&:to_sym)
|
84
87
|
|
85
|
-
I18n.available_locales
|
88
|
+
I18n.available_locales |= locales
|
86
89
|
|
87
90
|
locales.each do |locale|
|
88
91
|
I18n.backend.store_translations(locale, data[locale.to_s])
|
@@ -59,6 +59,7 @@ module Dry
|
|
59
59
|
super(tokens.merge(root: "#{tokens[:root]}.#{namespace}")) + super
|
60
60
|
end
|
61
61
|
|
62
|
+
# @api private
|
62
63
|
def rule_lookup_paths(tokens)
|
63
64
|
base_paths = messages.rule_lookup_paths(tokens)
|
64
65
|
base_paths.map { |key| key.gsub('dry_schema', "dry_schema.#{namespace}") } + base_paths
|
@@ -17,15 +17,15 @@ module Dry
|
|
17
17
|
TOKEN_REGEXP = /%{([\w\d]*)}/
|
18
18
|
|
19
19
|
# !@attribute [r] text
|
20
|
-
#
|
20
|
+
# @return [String]
|
21
21
|
attr_reader :text
|
22
22
|
|
23
23
|
# !@attribute [r] tokens
|
24
|
-
#
|
24
|
+
# @return [Hash]
|
25
25
|
attr_reader :tokens
|
26
26
|
|
27
27
|
# !@attribute [r] evaluator
|
28
|
-
#
|
28
|
+
# @return [Proc]
|
29
29
|
attr_reader :evaluator
|
30
30
|
|
31
31
|
# @api private
|
@@ -17,7 +17,15 @@ module Dry
|
|
17
17
|
|
18
18
|
include Dry::Equalizer(:data)
|
19
19
|
|
20
|
-
|
20
|
+
# Loaded localized message templates
|
21
|
+
#
|
22
|
+
# @return [Hash]
|
23
|
+
attr_reader :data
|
24
|
+
|
25
|
+
# Translation function
|
26
|
+
#
|
27
|
+
# @return [Proc]
|
28
|
+
attr_reader :t
|
21
29
|
|
22
30
|
# @api private
|
23
31
|
def self.build(options = EMPTY_HASH)
|
@@ -81,7 +89,7 @@ module Dry
|
|
81
89
|
|
82
90
|
# Merge messages from an additional path
|
83
91
|
#
|
84
|
-
# @param [String]
|
92
|
+
# @param [String] overrides
|
85
93
|
#
|
86
94
|
# @return [Messages::I18n]
|
87
95
|
#
|
@@ -2,21 +2,33 @@
|
|
2
2
|
|
3
3
|
module Dry
|
4
4
|
module Schema
|
5
|
+
# A special rule type that is configured under a specified namespace
|
6
|
+
#
|
7
|
+
# This is used internally to create rules that can be properly handled
|
8
|
+
# by the message compiler in situations where a schema reuses another schema
|
9
|
+
# but it is configured to use a message namespace
|
10
|
+
#
|
11
|
+
# @api private
|
5
12
|
class NamespacedRule
|
13
|
+
# @api private
|
6
14
|
attr_reader :rule
|
7
15
|
|
16
|
+
# @api private
|
8
17
|
attr_reader :namespace
|
9
18
|
|
19
|
+
# @api private
|
10
20
|
def initialize(namespace, rule)
|
11
21
|
@namespace = namespace
|
12
22
|
@rule = rule
|
13
23
|
end
|
14
24
|
|
25
|
+
# @api private
|
15
26
|
def call(input)
|
16
27
|
result = rule.call(input)
|
17
28
|
Logic::Result.new(result.success?) { [:namespace, [namespace, result.to_ast]] }
|
18
29
|
end
|
19
30
|
|
31
|
+
# @api private
|
20
32
|
def ast(input=Undefined)
|
21
33
|
[:namespace, [namespace, rule.ast(input)]]
|
22
34
|
end
|
data/lib/dry/schema/params.rb
CHANGED
data/lib/dry/schema/path.rb
CHANGED
@@ -8,15 +8,17 @@ module Dry
|
|
8
8
|
#
|
9
9
|
# @api private
|
10
10
|
class Path
|
11
|
+
include Comparable
|
11
12
|
include Enumerable
|
12
13
|
|
13
|
-
#
|
14
|
-
# @return [Array<Symbol>]
|
14
|
+
# @return [Array<Symbol>]
|
15
15
|
attr_reader :keys
|
16
16
|
|
17
|
+
alias_method :root, :first
|
18
|
+
|
17
19
|
# Coerce a spec into a path object
|
18
20
|
#
|
19
|
-
# @param [Symbol, String, Hash, Array<Symbol>] spec
|
21
|
+
# @param [Path, Symbol, String, Hash, Array<Symbol>] spec
|
20
22
|
#
|
21
23
|
# @return [Path]
|
22
24
|
#
|
@@ -29,10 +31,10 @@ module Dry
|
|
29
31
|
new(spec.split(DOT).map(&:to_sym))
|
30
32
|
when Hash
|
31
33
|
new(keys_from_hash(spec))
|
32
|
-
when
|
34
|
+
when Path
|
33
35
|
spec
|
34
36
|
else
|
35
|
-
raise ArgumentError, '+spec+ must be either a Symbol, Array or
|
37
|
+
raise ArgumentError, '+spec+ must be either a Symbol, Array, Hash or a Path'
|
36
38
|
end
|
37
39
|
end
|
38
40
|
|
@@ -62,12 +64,38 @@ module Dry
|
|
62
64
|
|
63
65
|
# @api private
|
64
66
|
def include?(other)
|
65
|
-
|
67
|
+
return false unless same_root?(other)
|
68
|
+
return false if index? && other.index? && !last.equal?(other.last)
|
69
|
+
self >= other
|
66
70
|
end
|
67
71
|
|
68
72
|
# @api private
|
69
73
|
def <=>(other)
|
70
|
-
|
74
|
+
raise ArgumentError, "Can't compare paths from different branches" unless same_root?(other)
|
75
|
+
|
76
|
+
return 0 if keys.eql?(other.keys)
|
77
|
+
|
78
|
+
res =
|
79
|
+
map { |key| (idx = other.index(key)) && keys[idx].equal?(key) }
|
80
|
+
.compact
|
81
|
+
.reject { |value| value.equal?(false) }
|
82
|
+
|
83
|
+
res.size < count ? 1 : -1
|
84
|
+
end
|
85
|
+
|
86
|
+
# @api private
|
87
|
+
def last
|
88
|
+
keys.last
|
89
|
+
end
|
90
|
+
|
91
|
+
# @api private
|
92
|
+
def same_root?(other)
|
93
|
+
root.equal?(other.root)
|
94
|
+
end
|
95
|
+
|
96
|
+
# @api private
|
97
|
+
def index?
|
98
|
+
last.is_a?(Integer)
|
71
99
|
end
|
72
100
|
end
|
73
101
|
end
|
data/lib/dry/schema/predicate.rb
CHANGED
@@ -21,6 +21,10 @@ module Dry
|
|
21
21
|
@predicate = predicate
|
22
22
|
end
|
23
23
|
|
24
|
+
# Dump negated predicate to an AST
|
25
|
+
#
|
26
|
+
# @return [Array]
|
27
|
+
#
|
24
28
|
# @api private
|
25
29
|
def to_ast(*args)
|
26
30
|
[:not, predicate.to_ast(*args)]
|
@@ -53,6 +57,9 @@ module Dry
|
|
53
57
|
|
54
58
|
# Negate a predicate
|
55
59
|
#
|
60
|
+
# @example
|
61
|
+
# required(:name).value(:string) { !empty? }
|
62
|
+
#
|
56
63
|
# @return [Negation]
|
57
64
|
#
|
58
65
|
# @api public
|
@@ -67,11 +74,17 @@ module Dry
|
|
67
74
|
end
|
68
75
|
end
|
69
76
|
|
77
|
+
# Compile predicate to a rule object
|
78
|
+
#
|
70
79
|
# @api private
|
71
80
|
def to_rule
|
72
81
|
compiler.visit(to_ast)
|
73
82
|
end
|
74
83
|
|
84
|
+
# Dump predicate to an AST
|
85
|
+
#
|
86
|
+
# @return [Array]
|
87
|
+
#
|
75
88
|
# @api private
|
76
89
|
def to_ast(*)
|
77
90
|
[:predicate, [name, compiler.predicates.arg_list(name, *args)]]
|
@@ -28,9 +28,8 @@ module Dry
|
|
28
28
|
#
|
29
29
|
# @api private
|
30
30
|
class Compiler
|
31
|
-
#
|
32
|
-
#
|
33
|
-
# @api private
|
31
|
+
# @return [PredicateRegistry]
|
32
|
+
# @api private
|
34
33
|
attr_reader :registry
|
35
34
|
|
36
35
|
# @api private
|
@@ -72,9 +71,8 @@ module Dry
|
|
72
71
|
end
|
73
72
|
|
74
73
|
# @api private
|
75
|
-
def
|
76
|
-
|
77
|
-
visit(other)
|
74
|
+
def visit_lax(node)
|
75
|
+
visit(node)
|
78
76
|
end
|
79
77
|
|
80
78
|
# @api private
|
@@ -114,9 +112,8 @@ module Dry
|
|
114
112
|
end
|
115
113
|
end
|
116
114
|
|
117
|
-
#
|
118
|
-
#
|
119
|
-
# @api private
|
115
|
+
# @return [Compiler]
|
116
|
+
# @api private
|
120
117
|
attr_reader :compiler
|
121
118
|
|
122
119
|
# @api private
|
data/lib/dry/schema/processor.rb
CHANGED
@@ -33,16 +33,17 @@ module Dry
|
|
33
33
|
param :steps, default: -> { EMPTY_ARRAY.dup }
|
34
34
|
|
35
35
|
class << self
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
36
|
+
# Return DSL configured via #define
|
37
|
+
#
|
38
|
+
# @return [DSL]
|
39
|
+
# @api private
|
40
40
|
attr_reader :definition
|
41
41
|
|
42
42
|
# Define a schema for your processor class
|
43
43
|
#
|
44
|
-
# @see
|
45
|
-
# @see
|
44
|
+
# @see Schema#define
|
45
|
+
# @see Schema#Params
|
46
|
+
# @see Schema#JSON
|
46
47
|
#
|
47
48
|
# @return [Class]
|
48
49
|
#
|
data/lib/dry/schema/result.rb
CHANGED
@@ -19,8 +19,11 @@ module Dry
|
|
19
19
|
|
20
20
|
# @api private
|
21
21
|
param :output
|
22
|
+
|
23
|
+
# Dump result to a hash returning processed and validated data
|
24
|
+
#
|
25
|
+
# @return [Hash]
|
22
26
|
alias_method :to_h, :output
|
23
|
-
alias_method :to_hash, :output
|
24
27
|
|
25
28
|
# @api private
|
26
29
|
param :results, default: -> { EMPTY_ARRAY.dup }
|
@@ -72,7 +75,7 @@ module Dry
|
|
72
75
|
|
73
76
|
# Check if there's an error for the provided spec
|
74
77
|
#
|
75
|
-
# @param [Symbol, Hash<Symbol=>Symbol>]
|
78
|
+
# @param [Symbol, Hash<Symbol=>Symbol>] spec
|
76
79
|
#
|
77
80
|
# @return [Boolean]
|
78
81
|
#
|
@@ -103,7 +106,7 @@ module Dry
|
|
103
106
|
#
|
104
107
|
# @see #message_set
|
105
108
|
#
|
106
|
-
# @return [
|
109
|
+
# @return [MessageSet]
|
107
110
|
#
|
108
111
|
# @api public
|
109
112
|
def errors(options = EMPTY_HASH)
|
data/lib/dry/schema/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dry-schema
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Piotr Solnica
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-05-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -156,7 +156,10 @@ dependencies:
|
|
156
156
|
- - ">="
|
157
157
|
- !ruby/object:Gem::Version
|
158
158
|
version: '0'
|
159
|
-
description:
|
159
|
+
description: |
|
160
|
+
dry-schema provides a DSL for defining schemas with keys and rules that should be applied to
|
161
|
+
values. It supports coercion, input sanitization, custom types and localized error messages
|
162
|
+
(with or without I18n gem). It's also used as the schema engine in dry-validation.
|
160
163
|
email:
|
161
164
|
- piotr.solnica@gmail.com
|
162
165
|
executables: []
|
@@ -244,5 +247,5 @@ requirements: []
|
|
244
247
|
rubygems_version: 3.0.3
|
245
248
|
signing_key:
|
246
249
|
specification_version: 4
|
247
|
-
summary:
|
250
|
+
summary: Coercion and validation for data structures
|
248
251
|
test_files: []
|