nxt_schema 0.1.2 → 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/.ruby-version +1 -0
- data/Gemfile +0 -1
- data/Gemfile.lock +32 -29
- data/README.md +186 -116
- data/lib/nxt_schema.rb +56 -49
- data/lib/nxt_schema/{node.rb → application.rb} +1 -1
- data/lib/nxt_schema/application/any_of.rb +40 -0
- data/lib/nxt_schema/application/base.rb +116 -0
- data/lib/nxt_schema/application/collection.rb +57 -0
- data/lib/nxt_schema/application/error_store.rb +57 -0
- data/lib/nxt_schema/application/errors/schema_error.rb +15 -0
- data/lib/nxt_schema/application/errors/validation_error.rb +15 -0
- data/lib/nxt_schema/application/leaf.rb +15 -0
- data/lib/nxt_schema/application/schema.rb +114 -0
- data/lib/nxt_schema/callable.rb +21 -55
- data/lib/nxt_schema/dsl.rb +41 -31
- data/lib/nxt_schema/error.rb +4 -0
- data/lib/nxt_schema/errors/invalid.rb +16 -0
- data/lib/nxt_schema/errors/{error.rb → invalid_options.rb} +1 -2
- data/lib/nxt_schema/missing_input.rb +9 -0
- data/lib/nxt_schema/node/any_of.rb +51 -0
- data/lib/nxt_schema/node/base.rb +135 -233
- data/lib/nxt_schema/node/collection.rb +10 -65
- data/lib/nxt_schema/node/has_sub_nodes.rb +81 -0
- data/lib/nxt_schema/node/leaf.rb +1 -31
- data/lib/nxt_schema/node/maybe_evaluator.rb +15 -10
- data/lib/nxt_schema/node/on_evaluator.rb +25 -0
- data/lib/nxt_schema/node/schema.rb +8 -134
- data/lib/nxt_schema/node/sub_nodes.rb +22 -0
- data/lib/nxt_schema/node/type_system_resolver.rb +22 -0
- data/lib/nxt_schema/types.rb +1 -1
- data/lib/nxt_schema/validators/attribute.rb +3 -3
- data/lib/nxt_schema/validators/{equality.rb → equal_to.rb} +5 -5
- data/lib/nxt_schema/validators/error_messages.rb +42 -0
- data/lib/nxt_schema/{error_messages → validators/error_messages}/en.yaml +3 -3
- data/lib/nxt_schema/validators/{excluded.rb → excluded_in.rb} +4 -4
- data/lib/nxt_schema/validators/excludes.rb +3 -3
- data/lib/nxt_schema/validators/greater_than.rb +3 -3
- data/lib/nxt_schema/validators/greater_than_or_equal.rb +3 -3
- data/lib/nxt_schema/validators/{included.rb → included_in.rb} +4 -4
- data/lib/nxt_schema/validators/includes.rb +3 -3
- data/lib/nxt_schema/validators/less_than.rb +3 -3
- data/lib/nxt_schema/validators/less_than_or_equal.rb +3 -3
- data/lib/nxt_schema/validators/optional_node.rb +13 -8
- data/lib/nxt_schema/validators/pattern.rb +3 -3
- data/lib/nxt_schema/validators/query.rb +4 -4
- data/lib/nxt_schema/validators/registry.rb +1 -7
- data/lib/nxt_schema/{node → validators}/validate_with_proxy.rb +8 -8
- data/lib/nxt_schema/validators/validator.rb +2 -2
- data/lib/nxt_schema/version.rb +1 -1
- data/nxt_schema.gemspec +1 -0
- metadata +42 -22
- data/lib/nxt_schema/callable_or_value.rb +0 -72
- data/lib/nxt_schema/error_messages.rb +0 -40
- data/lib/nxt_schema/errors.rb +0 -4
- data/lib/nxt_schema/errors/invalid_options_error.rb +0 -5
- data/lib/nxt_schema/errors/schema_not_applied_error.rb +0 -5
- data/lib/nxt_schema/node/constructor.rb +0 -9
- data/lib/nxt_schema/node/default_value_evaluator.rb +0 -20
- data/lib/nxt_schema/node/error.rb +0 -13
- data/lib/nxt_schema/node/has_subnodes.rb +0 -97
- data/lib/nxt_schema/node/template_store.rb +0 -15
- data/lib/nxt_schema/registry.rb +0 -85
- data/lib/nxt_schema/undefined.rb +0 -7
@@ -0,0 +1,114 @@
|
|
1
|
+
module NxtSchema
|
2
|
+
module Application
|
3
|
+
class Schema < Application::Base
|
4
|
+
def call
|
5
|
+
apply_on_evaluators
|
6
|
+
child_applications # build applications here so we can access them even when invalid
|
7
|
+
return self if maybe_evaluator_applies?
|
8
|
+
|
9
|
+
coerce_input
|
10
|
+
return self unless valid?
|
11
|
+
|
12
|
+
flag_missing_keys
|
13
|
+
apply_additional_keys_strategy
|
14
|
+
|
15
|
+
child_applications.each do |key, child|
|
16
|
+
current_application = child.call
|
17
|
+
|
18
|
+
if !current_application.valid?
|
19
|
+
merge_errors(current_application)
|
20
|
+
else
|
21
|
+
output[key] = current_application.output
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
transform_keys
|
26
|
+
register_as_applied_when_valid
|
27
|
+
run_validations
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
delegate :[], to: :child_applications
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def transform_keys
|
36
|
+
transformer = node.key_transformer
|
37
|
+
return unless transformer && output.respond_to?(:transform_keys!)
|
38
|
+
|
39
|
+
output.transform_keys!(&transformer)
|
40
|
+
end
|
41
|
+
|
42
|
+
def keys
|
43
|
+
@keys ||= node.sub_nodes.reject { |key, _| optional_and_not_given_key?(key) }.keys
|
44
|
+
end
|
45
|
+
|
46
|
+
def additional_keys
|
47
|
+
@additional_keys ||= input.keys - keys
|
48
|
+
end
|
49
|
+
|
50
|
+
def optional_and_not_given_key?(key)
|
51
|
+
node.sub_nodes[key].optional? && !input.key?(key)
|
52
|
+
end
|
53
|
+
|
54
|
+
def additional_keys?
|
55
|
+
additional_keys.any?
|
56
|
+
end
|
57
|
+
|
58
|
+
def missing_keys
|
59
|
+
@missing_keys ||= node.sub_nodes.reject { |_, node| node.omnipresent? || node.optional? }.keys - input.keys
|
60
|
+
end
|
61
|
+
|
62
|
+
def apply_additional_keys_strategy
|
63
|
+
return if allow_additional_keys?
|
64
|
+
return unless additional_keys?
|
65
|
+
|
66
|
+
if restrict_additional_keys?
|
67
|
+
add_schema_error("Additional keys are not allowed: #{additional_keys}")
|
68
|
+
elsif reject_additional_keys?
|
69
|
+
self.output = output.except(*additional_keys)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def flag_missing_keys
|
74
|
+
return if missing_keys.empty?
|
75
|
+
|
76
|
+
add_schema_error("The following keys are missing: #{missing_keys}")
|
77
|
+
end
|
78
|
+
|
79
|
+
def allow_additional_keys?
|
80
|
+
node.additional_keys_strategy == :allow
|
81
|
+
end
|
82
|
+
|
83
|
+
def reject_additional_keys?
|
84
|
+
node.additional_keys_strategy == :reject
|
85
|
+
end
|
86
|
+
|
87
|
+
def restrict_additional_keys?
|
88
|
+
node.additional_keys_strategy == :restrict
|
89
|
+
end
|
90
|
+
|
91
|
+
def child_applications
|
92
|
+
@child_applications ||= begin
|
93
|
+
keys.inject({}) do |acc, key|
|
94
|
+
child_application = build_child_application(key)
|
95
|
+
acc[key] = child_application if child_application.present?
|
96
|
+
acc
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def build_child_application(key)
|
102
|
+
sub_node = node.sub_nodes[key]
|
103
|
+
return unless sub_node.present?
|
104
|
+
|
105
|
+
value = input_has_key?(input, key) ? input[key] : MissingInput.new
|
106
|
+
sub_node.build_application(input: value, context: context, parent: self)
|
107
|
+
end
|
108
|
+
|
109
|
+
def input_has_key?(input, key)
|
110
|
+
input.respond_to?(:key?) && input.key?(key)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
data/lib/nxt_schema/callable.rb
CHANGED
@@ -1,74 +1,40 @@
|
|
1
1
|
module NxtSchema
|
2
2
|
class Callable
|
3
|
-
def initialize(
|
4
|
-
@
|
5
|
-
|
6
|
-
|
7
|
-
self.type = :method
|
8
|
-
elsif callee.respond_to?(:call)
|
9
|
-
self.type = :proc
|
10
|
-
self.context = callee.binding
|
11
|
-
else
|
12
|
-
raise ArgumentError, "Callee is nor symbol nor a proc: #{callee}"
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
def bind!(execution_context)
|
17
|
-
self.context = execution_context
|
18
|
-
ensure_context_not_missing
|
19
|
-
self
|
3
|
+
def initialize(callable, target = nil, *args)
|
4
|
+
@callable = callable
|
5
|
+
@target = target
|
6
|
+
@args = args
|
20
7
|
end
|
21
8
|
|
22
|
-
def
|
23
|
-
return
|
9
|
+
def call
|
10
|
+
return callable if value?
|
11
|
+
return callable.call(*args_from_arity) if proc?
|
24
12
|
|
25
|
-
|
26
|
-
ensure_context_not_missing
|
27
|
-
self
|
13
|
+
target.send(callable, *args_from_arity)
|
28
14
|
end
|
29
15
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
def call(*args)
|
34
|
-
ensure_context_not_missing
|
35
|
-
|
36
|
-
args = args.take(arity)
|
16
|
+
def method?
|
17
|
+
@method ||= callable.class.in?([Symbol, String]) && target.respond_to?(callable)
|
18
|
+
end
|
37
19
|
|
38
|
-
|
39
|
-
|
40
|
-
else
|
41
|
-
context.instance_exec(*args, &callee)
|
42
|
-
end
|
20
|
+
def proc?
|
21
|
+
@proc ||= callable.respond_to?(:call)
|
43
22
|
end
|
44
23
|
|
45
|
-
def
|
46
|
-
|
47
|
-
callee.arity
|
48
|
-
elsif method?
|
49
|
-
method = context.send(:method, callee)
|
50
|
-
method.arity
|
51
|
-
else
|
52
|
-
raise ArgumentError, "Can't resolve arity from #{callee}"
|
53
|
-
end
|
24
|
+
def value?
|
25
|
+
!method? && !proc?
|
54
26
|
end
|
55
27
|
|
56
28
|
private
|
57
29
|
|
58
|
-
|
59
|
-
type == :proc
|
60
|
-
end
|
30
|
+
attr_reader :callable, :target, :args
|
61
31
|
|
62
|
-
def
|
63
|
-
|
32
|
+
def arity
|
33
|
+
proc? ? callable.arity : 0
|
64
34
|
end
|
65
35
|
|
66
|
-
def
|
67
|
-
|
68
|
-
|
69
|
-
raise ArgumentError, "Missing context: #{context}"
|
36
|
+
def args_from_arity
|
37
|
+
@args_from_arity ||= ([target] + args).take(arity)
|
70
38
|
end
|
71
|
-
|
72
|
-
attr_accessor :context, :callee, :type
|
73
39
|
end
|
74
|
-
end
|
40
|
+
end
|
data/lib/nxt_schema/dsl.rb
CHANGED
@@ -1,38 +1,48 @@
|
|
1
1
|
module NxtSchema
|
2
|
-
|
3
|
-
|
4
|
-
end
|
2
|
+
module Dsl
|
3
|
+
DEFAULT_OPTIONS = { type_system: NxtSchema::Types }.freeze
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
def collection(name = :root, type: NxtSchema::Node::Collection::DEFAULT_TYPE, **options, &block)
|
6
|
+
NxtSchema::Node::Collection.new(
|
7
|
+
name: name,
|
8
|
+
type: type,
|
9
|
+
parent_node: nil,
|
10
|
+
**DEFAULT_OPTIONS.merge(options),
|
11
|
+
&block
|
12
|
+
)
|
13
|
+
end
|
9
14
|
|
10
|
-
|
11
|
-
Node::Schema.new(
|
12
|
-
name: name,
|
13
|
-
parent_node: nil,
|
14
|
-
**options.merge(
|
15
|
-
type_system: NxtSchema::Types::Params,
|
16
|
-
).reverse_merge(transform_keys: :to_sym),
|
17
|
-
&block
|
18
|
-
)
|
19
|
-
end
|
15
|
+
alias nodes collection
|
20
16
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
17
|
+
def schema(name = :roots, type: NxtSchema::Node::Schema::DEFAULT_TYPE, **options, &block)
|
18
|
+
NxtSchema::Node::Schema.new(
|
19
|
+
name: name,
|
20
|
+
type: type,
|
21
|
+
parent_node: nil,
|
22
|
+
**DEFAULT_OPTIONS.merge(options),
|
23
|
+
&block
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
def any_of(name = :roots, **options, &block)
|
28
|
+
NxtSchema::Node::AnyOf.new(
|
29
|
+
name: name,
|
30
|
+
parent_node: nil,
|
31
|
+
**DEFAULT_OPTIONS.merge(options),
|
32
|
+
&block
|
33
|
+
)
|
34
|
+
end
|
31
35
|
|
32
|
-
|
33
|
-
alias_method :root, :schema
|
34
|
-
alias_method :nodes, :collection
|
35
|
-
alias_method :roots, :collection
|
36
|
+
# schema root with NxtSchema::Types::Params type system
|
36
37
|
|
37
|
-
|
38
|
+
def params(name = :params, type: NxtSchema::Node::Schema::DEFAULT_TYPE, **options, &block)
|
39
|
+
NxtSchema::Node::Schema.new(
|
40
|
+
name: name,
|
41
|
+
type: type,
|
42
|
+
parent_node: nil,
|
43
|
+
**options.merge(type_system: NxtSchema::Types::Params),
|
44
|
+
&block
|
45
|
+
)
|
46
|
+
end
|
47
|
+
end
|
38
48
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module NxtSchema
|
2
|
+
module Node
|
3
|
+
class AnyOf < Base
|
4
|
+
include HasSubNodes
|
5
|
+
|
6
|
+
def initialize(name:, type: nil, parent_node:, **options, &block)
|
7
|
+
super
|
8
|
+
end
|
9
|
+
|
10
|
+
def collection(name = sub_nodes.count, type = NxtSchema::Node::Collection::DEFAULT_TYPE, **options, &block)
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
def schema(name = sub_nodes.count, type = NxtSchema::Node::Schema::DEFAULT_TYPE, **options, &block)
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
def node(name = sub_nodes.count, node_or_type_of_node = nil, **options, &block)
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
# TODO: Maybe overwrite sub node methods to not have to provide a name here and use node count instead
|
23
|
+
|
24
|
+
def on(*args)
|
25
|
+
raise NotImplementedError
|
26
|
+
end
|
27
|
+
|
28
|
+
def maybe(*args)
|
29
|
+
raise NotImplementedError
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def resolve_type(name_or_type)
|
35
|
+
nil
|
36
|
+
end
|
37
|
+
|
38
|
+
def resolve_optional_option
|
39
|
+
return unless options.key?(:optional)
|
40
|
+
|
41
|
+
raise InvalidOptions, "The optional option is not available for nodes of type #{self.class.name}"
|
42
|
+
end
|
43
|
+
|
44
|
+
def resolve_omnipresent_option
|
45
|
+
return unless options.key?(:omnipresent)
|
46
|
+
|
47
|
+
raise InvalidOptions, "The omnipresent option is not available for nodes of type #{self.class.name}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/nxt_schema/node/base.rb
CHANGED
@@ -1,315 +1,217 @@
|
|
1
1
|
module NxtSchema
|
2
2
|
module Node
|
3
3
|
class Base
|
4
|
-
def initialize(name
|
5
|
-
|
4
|
+
def initialize(name:, type:, parent_node:, **options, &block)
|
5
|
+
resolve_name(name)
|
6
|
+
|
6
7
|
@parent_node = parent_node
|
7
8
|
@options = options
|
8
|
-
@
|
9
|
-
@
|
10
|
-
@
|
11
|
-
@
|
9
|
+
@is_root_node = parent_node.nil?
|
10
|
+
@root_node = parent_node.nil? ? self : parent_node.root_node
|
11
|
+
@path = resolve_path
|
12
|
+
@on_evaluators = []
|
13
|
+
@maybe_evaluators = []
|
12
14
|
@validations = []
|
13
|
-
@
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
# Note that it is not possible to use present? on an instance of NxtSchema::Schema since it inherits from Hash
|
25
|
-
evaluate_block(block) if block_given?
|
15
|
+
@configuration = block
|
16
|
+
|
17
|
+
resolve_key_transformer
|
18
|
+
resolve_context
|
19
|
+
resolve_optional_option
|
20
|
+
resolve_omnipresent_option
|
21
|
+
resolve_type_system
|
22
|
+
resolve_type(type)
|
23
|
+
resolve_additional_keys_strategy
|
24
|
+
application_class # memoize
|
25
|
+
configure(&block) if block_given?
|
26
26
|
end
|
27
27
|
|
28
28
|
attr_accessor :name,
|
29
29
|
:parent_node,
|
30
30
|
:options,
|
31
31
|
:type,
|
32
|
-
:
|
33
|
-
:
|
34
|
-
:errors,
|
35
|
-
:validations,
|
36
|
-
:schema_errors_key,
|
37
|
-
:level,
|
38
|
-
:validation_errors,
|
39
|
-
:all_nodes,
|
40
|
-
:value,
|
41
|
-
:type_system,
|
42
|
-
:root,
|
43
|
-
:context,
|
44
|
-
:applied,
|
45
|
-
:input,
|
46
|
-
:additional_keys_strategy,
|
47
|
-
:locale
|
48
|
-
|
49
|
-
|
50
|
-
alias_method :types, :type_system
|
51
|
-
|
52
|
-
def parent(level = 1)
|
53
|
-
level.times.inject(self) { |acc| acc.parent_node }
|
54
|
-
end
|
32
|
+
:root_node,
|
33
|
+
:additional_keys_strategy
|
55
34
|
|
56
|
-
|
35
|
+
attr_reader :type_system,
|
36
|
+
:path,
|
37
|
+
:context,
|
38
|
+
:meta,
|
39
|
+
:on_evaluators,
|
40
|
+
:maybe_evaluators,
|
41
|
+
:validations,
|
42
|
+
:configuration,
|
43
|
+
:key_transformer
|
57
44
|
|
58
|
-
def
|
59
|
-
|
60
|
-
evaluate_block(block) if block_given?
|
61
|
-
self
|
45
|
+
def apply(input: MissingInput.new, context: self.context, parent: nil, error_key: nil)
|
46
|
+
build_application(input: input, context: context, parent: parent, error_key: error_key).call
|
62
47
|
end
|
63
48
|
|
64
|
-
def
|
65
|
-
|
66
|
-
|
67
|
-
else
|
68
|
-
@meta = value
|
69
|
-
self
|
70
|
-
end
|
71
|
-
end
|
49
|
+
def apply!(input: MissingInput.new, context: self.context, parent: nil, error_key: nil)
|
50
|
+
result = build_application(input: input, context: context, parent: parent, error_key: error_key).call
|
51
|
+
return result if parent || result.errors.empty?
|
72
52
|
|
73
|
-
|
74
|
-
if !value && options.key?(:default)
|
75
|
-
DefaultValueEvaluator.new(self, options.fetch(:default)).call
|
76
|
-
else
|
77
|
-
value
|
78
|
-
end
|
53
|
+
raise NxtSchema::Errors::Invalid.new(result)
|
79
54
|
end
|
80
55
|
|
81
|
-
def
|
82
|
-
|
83
|
-
|
84
|
-
|
56
|
+
def build_application(input: MissingInput.new, context: self.context, parent: nil, error_key: nil)
|
57
|
+
application_class.new(
|
58
|
+
node: self,
|
59
|
+
input: input,
|
60
|
+
parent: parent,
|
61
|
+
context: context,
|
62
|
+
error_key: error_key
|
63
|
+
)
|
85
64
|
end
|
86
65
|
|
87
|
-
def
|
88
|
-
|
89
|
-
|
90
|
-
options.merge!(optional: optional_value || block)
|
91
|
-
self
|
66
|
+
def root_node?
|
67
|
+
@is_root_node
|
92
68
|
end
|
93
69
|
|
94
|
-
def
|
95
|
-
|
96
|
-
|
97
|
-
options.merge!(presence: presence_value || block)
|
98
|
-
self
|
70
|
+
def optional?
|
71
|
+
@optional
|
99
72
|
end
|
100
73
|
|
101
|
-
def
|
102
|
-
@
|
103
|
-
presence_option = options[:presence]
|
104
|
-
|
105
|
-
options[:presence] = if presence_option.respond_to?(:call)
|
106
|
-
Callable.new(presence_option).call(self, value)
|
107
|
-
else
|
108
|
-
presence_option
|
109
|
-
end
|
110
|
-
end
|
74
|
+
def omnipresent?
|
75
|
+
@omnipresent
|
111
76
|
end
|
112
77
|
|
113
|
-
def
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
validator = key
|
118
|
-
else
|
119
|
-
raise ArgumentError, "Don't know how to resolve validator from: #{key}"
|
120
|
-
end
|
78
|
+
def default(value = NxtSchema::MissingInput.new, &block)
|
79
|
+
value = missing_input?(value) ? block : value
|
80
|
+
condition = ->(input) { missing_input?(input) || input.nil? }
|
81
|
+
on(condition, value)
|
121
82
|
|
122
|
-
add_validators(validator)
|
123
|
-
evaluate_block(block) if block_given?
|
124
83
|
self
|
125
84
|
end
|
126
85
|
|
127
|
-
def
|
128
|
-
|
129
|
-
|
130
|
-
false
|
131
|
-
end
|
132
|
-
|
133
|
-
def validate_all_nodes
|
134
|
-
sorted_nodes = all_nodes.values.sort do |node, other_node|
|
135
|
-
[node.level, (!node.leaf?).to_s] <=> [other_node.level, (!other_node.leaf?).to_s]
|
136
|
-
end
|
137
|
-
|
138
|
-
# we have to start from the bottom, leafs before others on the same level
|
139
|
-
sorted_nodes.reverse_each(&:apply_validations)
|
140
|
-
end
|
141
|
-
|
142
|
-
def apply_validations
|
143
|
-
# We don't run validations in case there are schema errors
|
144
|
-
# to avoid weird errors
|
145
|
-
# First reject empty schema_errors
|
146
|
-
schema_errors.reject! { |_, v| v.empty? }
|
147
|
-
|
148
|
-
# TODO: Is this correct? - Do not apply validations when maybe criteria applies?
|
149
|
-
unless schema_errors[schema_errors_key]&.any? && !maybe_criteria_applies?(value)
|
150
|
-
build_validations
|
151
|
-
|
152
|
-
validations.each do |validation|
|
153
|
-
args = [self, value]
|
154
|
-
validation.call(*args.take(validation.arity))
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
|
-
if self.is_a?(NxtSchema::Node::Collection) && value.respond_to?(:each)
|
159
|
-
value.each_with_index do |item, index|
|
160
|
-
validation_errors[index]&.reject! { |_, v| v.empty? }
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
validation_errors.reject! { |_, v| v.empty? }
|
86
|
+
def on(condition, value = NxtSchema::MissingInput.new, &block)
|
87
|
+
value = missing_input?(value) ? block : value
|
88
|
+
on_evaluators << OnEvaluator.new(condition: condition, value: value)
|
165
89
|
|
166
90
|
self
|
167
91
|
end
|
168
92
|
|
169
|
-
def
|
170
|
-
|
171
|
-
|
172
|
-
end
|
173
|
-
|
174
|
-
def schema_errors?
|
175
|
-
schema_errors.reject! { |_, v| v.empty? }
|
176
|
-
schema_errors.any?
|
177
|
-
end
|
93
|
+
def maybe(value = NxtSchema::MissingInput.new, &block)
|
94
|
+
value = missing_input?(value) ? block : value
|
95
|
+
maybe_evaluators << MaybeEvaluator.new(value: value)
|
178
96
|
|
179
|
-
|
180
|
-
validation_errors.reject! { |_, v| v.empty? }
|
181
|
-
validation_errors.any?
|
182
|
-
end
|
183
|
-
|
184
|
-
def root?
|
185
|
-
@is_root
|
97
|
+
self
|
186
98
|
end
|
187
99
|
|
188
|
-
def
|
189
|
-
|
190
|
-
end
|
100
|
+
def validate(key = NxtSchema::MissingInput.new, *args, &block)
|
101
|
+
# TODO: This does not really work with all kinds of chaining combinations yet!
|
191
102
|
|
192
|
-
|
193
|
-
|
103
|
+
validator = if key.is_a?(Symbol)
|
104
|
+
validator(key, *args)
|
105
|
+
elsif key.respond_to?(:call)
|
106
|
+
key
|
107
|
+
elsif block_given?
|
108
|
+
if key.is_a?(NxtSchema::MissingInput)
|
109
|
+
block
|
110
|
+
else
|
111
|
+
configure(&block)
|
112
|
+
end
|
113
|
+
else
|
114
|
+
raise ArgumentError, "Don't know how to resolve validator from: #{key} with: #{args} #{block}"
|
115
|
+
end
|
194
116
|
|
195
|
-
|
196
|
-
end
|
117
|
+
register_validator(validator)
|
197
118
|
|
198
|
-
|
199
|
-
options[:validate] ||= []
|
200
|
-
options[:validate] = Array(options.fetch(:validate, []))
|
201
|
-
options[:validate] << validator
|
202
|
-
end
|
203
|
-
|
204
|
-
def validator(key, *args)
|
205
|
-
Validators::Registry::VALIDATORS.resolve(key).new(*args).build
|
119
|
+
self
|
206
120
|
end
|
207
121
|
|
208
122
|
def validate_with(&block)
|
209
|
-
|
210
|
-
|
211
|
-
)
|
123
|
+
proxy = ->(node) { NxtSchema::Validator::ValidateWithProxy.new(node).validate(&block) }
|
124
|
+
register_validator(proxy)
|
212
125
|
end
|
213
126
|
|
214
127
|
private
|
215
128
|
|
216
|
-
|
217
|
-
return if all_nodes.key?(object_id)
|
129
|
+
attr_writer :path, :meta, :context, :on_evaluators, :maybe_evaluators
|
218
130
|
|
219
|
-
|
220
|
-
|
131
|
+
def validator(key, *args)
|
132
|
+
Validators::REGISTRY.resolve!(key).new(*args).build
|
221
133
|
end
|
222
134
|
|
223
|
-
def
|
224
|
-
|
135
|
+
def register_validator(validator)
|
136
|
+
validations << validator
|
225
137
|
end
|
226
138
|
|
227
|
-
def
|
228
|
-
|
139
|
+
def resolve_type(name_or_type)
|
140
|
+
@type = root_node.send(:type_resolver).resolve(type_system, name_or_type)
|
229
141
|
end
|
230
142
|
|
231
|
-
def
|
232
|
-
|
233
|
-
schema_errors[index] << error
|
234
|
-
|
235
|
-
add_error(error, index)
|
143
|
+
def resolve_type_system
|
144
|
+
@type_system = TypeSystemResolver.new(node: self).call
|
236
145
|
end
|
237
146
|
|
238
|
-
def
|
239
|
-
@
|
240
|
-
|
147
|
+
def type_resolver
|
148
|
+
@type_resolver ||= begin
|
149
|
+
root_node? ? TypeResolver.new : (raise NoMethodError, 'type_resolver is only available on root node')
|
241
150
|
end
|
242
151
|
end
|
243
152
|
|
244
|
-
def
|
245
|
-
|
246
|
-
validate_all_nodes if root?
|
247
|
-
self.errors = flat_validation_errors(validation_errors, name)
|
248
|
-
self
|
153
|
+
def application_class
|
154
|
+
@application_class ||= "NxtSchema::Application::#{self.class.name.demodulize}".constantize
|
249
155
|
end
|
250
156
|
|
251
|
-
def
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
flat_validation_errors(val, current_namespace, acc)
|
257
|
-
else
|
258
|
-
acc[current_namespace] ||= []
|
259
|
-
acc[current_namespace] += Array(val)
|
260
|
-
end
|
157
|
+
def configure(&block)
|
158
|
+
if block.arity == 1
|
159
|
+
block.call(self)
|
160
|
+
else
|
161
|
+
instance_exec(&block)
|
261
162
|
end
|
262
163
|
end
|
263
164
|
|
264
|
-
def
|
265
|
-
|
266
|
-
|
267
|
-
size + 1
|
268
|
-
else
|
269
|
-
raise ArgumentError, "Nodes with parent_node: #{parent_node} cannot be anonymous"
|
270
|
-
end
|
271
|
-
else
|
272
|
-
:root
|
165
|
+
def resolve_additional_keys_strategy
|
166
|
+
@additional_keys_strategy = options.fetch(:additional_keys) do
|
167
|
+
parent_node&.send(:additional_keys_strategy) || :allow
|
273
168
|
end
|
274
169
|
end
|
275
170
|
|
276
|
-
def
|
277
|
-
|
278
|
-
|
171
|
+
def resolve_optional_option
|
172
|
+
optional = options.fetch(:optional, false)
|
173
|
+
raise Errors::InvalidOptions, 'Optional nodes are only available within schemas' if optional && !parent_node.is_a?(Schema)
|
174
|
+
raise Errors::InvalidOptions, "Can't make omnipresent node optional" if optional && omnipresent?
|
175
|
+
|
176
|
+
if optional.respond_to?(:call)
|
177
|
+
# When a node is conditionally optional we make it optional and add a validator to the parent to check
|
178
|
+
# that it's there when the option does not apply.
|
179
|
+
optional_node_validator = validator(:optional_node, optional, name)
|
180
|
+
parent_node.send(:register_validator, optional_node_validator)
|
181
|
+
@optional = true
|
279
182
|
else
|
280
|
-
|
281
|
-
block.call(*evaluator_args.take(block.arity))
|
183
|
+
@optional = optional
|
282
184
|
end
|
283
185
|
end
|
284
186
|
|
285
|
-
def
|
286
|
-
|
187
|
+
def resolve_omnipresent_option
|
188
|
+
omnipresent = options.fetch(:omnipresent, false)
|
189
|
+
raise Errors::InvalidOptions, 'Omnipresent nodes are only available within schemas' if omnipresent && !parent_node.is_a?(Schema)
|
190
|
+
raise Errors::InvalidOptions, "Can't make omnipresent node optional" if optional? && omnipresent
|
287
191
|
|
288
|
-
|
289
|
-
type_system
|
290
|
-
elsif type_system.is_a?(Symbol) || type_system.is_a?(String)
|
291
|
-
"NxtSchema::Types::#{type_system.to_s.classify}".constantize
|
292
|
-
else
|
293
|
-
NxtSchema::Types
|
294
|
-
end
|
192
|
+
@omnipresent = omnipresent
|
295
193
|
end
|
296
194
|
|
297
|
-
def
|
298
|
-
|
195
|
+
def resolve_path
|
196
|
+
self.path = root_node? ? name : "#{parent_node.path}.#{name}"
|
299
197
|
end
|
300
198
|
|
301
|
-
def
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
end
|
308
|
-
end
|
199
|
+
def resolve_context
|
200
|
+
self.context = options.fetch(:context) { parent_node&.send(:context) }
|
201
|
+
end
|
202
|
+
|
203
|
+
def missing_input?(value)
|
204
|
+
value.is_a? MissingInput
|
309
205
|
end
|
310
206
|
|
311
|
-
def
|
312
|
-
|
207
|
+
def resolve_key_transformer
|
208
|
+
@key_transformer = options.fetch(:transform_keys) { parent_node&.key_transformer || ->(key) { key.to_sym } }
|
209
|
+
end
|
210
|
+
|
211
|
+
def resolve_name(name)
|
212
|
+
raise ArgumentError, 'Name can either be a symbol or an integer' unless name.class.in?([Symbol, Integer])
|
213
|
+
|
214
|
+
@name = name
|
313
215
|
end
|
314
216
|
end
|
315
217
|
end
|