nxt_schema 0.1.0 → 1.0.2
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 +40 -42
- data/README.md +267 -121
- data/lib/nxt_schema.rb +60 -51
- 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/{error.rb → coercion_error.rb} +1 -2
- data/lib/nxt_schema/errors/invalid.rb +16 -0
- data/lib/nxt_schema/errors/invalid_options.rb +6 -0
- data/lib/nxt_schema/node/any_of.rb +39 -0
- data/lib/nxt_schema/node/base.rb +66 -267
- data/lib/nxt_schema/node/collection.rb +40 -56
- data/lib/nxt_schema/node/error_store.rb +41 -0
- data/lib/nxt_schema/node/errors/schema_error.rb +15 -0
- data/lib/nxt_schema/node/errors/validation_error.rb +15 -0
- data/lib/nxt_schema/node/leaf.rb +8 -36
- data/lib/nxt_schema/node/schema.rb +70 -103
- data/lib/nxt_schema/registry.rb +12 -74
- data/lib/nxt_schema/registry/proxy.rb +21 -0
- data/lib/nxt_schema/template/any_of.rb +50 -0
- data/lib/nxt_schema/template/base.rb +220 -0
- data/lib/nxt_schema/template/collection.rb +23 -0
- data/lib/nxt_schema/template/has_sub_nodes.rb +87 -0
- data/lib/nxt_schema/template/leaf.rb +13 -0
- data/lib/nxt_schema/template/maybe_evaluator.rb +28 -0
- data/lib/nxt_schema/template/on_evaluator.rb +25 -0
- data/lib/nxt_schema/template/schema.rb +22 -0
- data/lib/nxt_schema/template/sub_nodes.rb +22 -0
- data/lib/nxt_schema/template/type_resolver.rb +39 -0
- data/lib/nxt_schema/template/type_system_resolver.rb +22 -0
- data/lib/nxt_schema/types.rb +7 -4
- data/lib/nxt_schema/undefined.rb +4 -2
- data/lib/nxt_schema/validators/{equality.rb → equal_to.rb} +2 -2
- data/lib/nxt_schema/validators/error_messages.rb +42 -0
- data/lib/nxt_schema/{error_messages → validators/error_messages}/en.yaml +6 -5
- data/lib/nxt_schema/validators/{excluded.rb → excluded_in.rb} +1 -1
- data/lib/nxt_schema/validators/{included.rb → included_in.rb} +1 -1
- data/lib/nxt_schema/validators/includes.rb +1 -1
- data/lib/nxt_schema/validators/optional_node.rb +11 -6
- data/lib/nxt_schema/validators/registry.rb +1 -7
- data/lib/nxt_schema/{node → validators}/validate_with_proxy.rb +3 -3
- data/lib/nxt_schema/validators/validator.rb +2 -2
- data/lib/nxt_schema/version.rb +1 -1
- data/nxt_schema.gemspec +1 -0
- metadata +44 -21
- 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/maybe_evaluator.rb +0 -23
- data/lib/nxt_schema/node/template_store.rb +0 -15
- data/lib/nxt_schema/node/type_resolver.rb +0 -24
@@ -1,72 +1,56 @@
|
|
1
1
|
module NxtSchema
|
2
2
|
module Node
|
3
3
|
class Collection < Node::Base
|
4
|
-
def
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
self.value = value_or_default_value(value)
|
23
|
-
|
24
|
-
unless maybe_criteria_applies?(value)
|
25
|
-
self.value = coerce_value(value)
|
4
|
+
def call
|
5
|
+
apply_on_evaluators
|
6
|
+
child_nodes # build nodes here so we can access them even when invalid
|
7
|
+
return self if maybe_evaluator_applies?
|
8
|
+
|
9
|
+
coerce_input
|
10
|
+
validate_filled
|
11
|
+
return self unless valid?
|
12
|
+
|
13
|
+
child_nodes.each_with_index do |item, index|
|
14
|
+
child_node = item.call
|
15
|
+
|
16
|
+
if !child_node.valid?
|
17
|
+
merge_errors(child_node)
|
18
|
+
else
|
19
|
+
output[index] = child_node.output
|
20
|
+
end
|
21
|
+
end
|
26
22
|
|
27
|
-
|
23
|
+
register_as_coerced_when_no_errors
|
24
|
+
run_validations
|
28
25
|
|
29
|
-
|
30
|
-
|
31
|
-
validation_errors[index] ||= { schema_errors_key => [] }
|
26
|
+
self
|
27
|
+
end
|
32
28
|
|
33
|
-
|
34
|
-
current_node = node.dup
|
35
|
-
current_node_store[node_name] = current_node
|
36
|
-
current_node.apply(item, parent_node: self, context: context)
|
37
|
-
value_store[index] = current_node.value
|
29
|
+
delegate :[], to: :child_nodes
|
38
30
|
|
39
|
-
|
40
|
-
unless current_node.schema_errors?
|
41
|
-
current_node_store.each do |node_name, node|
|
42
|
-
node.schema_errors = { }
|
43
|
-
node.validation_errors = { }
|
44
|
-
item_schema_errors = schema_errors[index][node_name] = node.schema_errors
|
45
|
-
validation_errors[index][node_name] = node.validation_errors
|
46
|
-
end
|
31
|
+
private
|
47
32
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
validation_errors[index][node_name] = current_node.validation_errors
|
52
|
-
end
|
53
|
-
end
|
33
|
+
def validate_filled
|
34
|
+
add_schema_error('is not allowed to be empty') if input.blank? && !maybe_evaluator_applies?
|
35
|
+
end
|
54
36
|
|
55
|
-
|
56
|
-
|
37
|
+
def child_nodes
|
38
|
+
@child_nodes ||= begin
|
39
|
+
return [] unless input.respond_to?(:each_with_index)
|
57
40
|
|
58
|
-
|
59
|
-
|
60
|
-
self.value = value_store
|
41
|
+
input.each_with_index.map do |item, index|
|
42
|
+
build_child_node(item, index)
|
61
43
|
end
|
62
44
|
end
|
63
45
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
46
|
+
end
|
47
|
+
|
48
|
+
def build_child_node(item, error_key)
|
49
|
+
sub_node.build_node(input: item, context: context, parent: self, error_key: error_key)
|
50
|
+
end
|
51
|
+
|
52
|
+
def sub_node
|
53
|
+
@sub_node ||= node.sub_nodes.values.first
|
70
54
|
end
|
71
55
|
end
|
72
56
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module NxtSchema
|
2
|
+
module Node
|
3
|
+
class ErrorStore < ::Hash
|
4
|
+
def initialize(node)
|
5
|
+
super()
|
6
|
+
@node = node
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :node
|
10
|
+
|
11
|
+
def add_schema_error(message:)
|
12
|
+
add_error(
|
13
|
+
node,
|
14
|
+
NxtSchema::Node::Errors::SchemaError.new(
|
15
|
+
node: node,
|
16
|
+
message: message
|
17
|
+
)
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_validation_error(message:)
|
22
|
+
add_error(
|
23
|
+
node,
|
24
|
+
NxtSchema::Node::Errors::ValidationError.new(
|
25
|
+
node: node,
|
26
|
+
message: message
|
27
|
+
)
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
def merge_errors(node)
|
32
|
+
merge!(node.errors)
|
33
|
+
end
|
34
|
+
|
35
|
+
def add_error(node, error)
|
36
|
+
self[node.error_key] ||= []
|
37
|
+
self[node.error_key] << error
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/nxt_schema/node/leaf.rb
CHANGED
@@ -1,42 +1,14 @@
|
|
1
1
|
module NxtSchema
|
2
2
|
module Node
|
3
3
|
class Leaf < Node::Base
|
4
|
-
def
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
def apply(input, parent_node: self.parent_node, context: nil)
|
14
|
-
self.input = input
|
15
|
-
register_node(context)
|
16
|
-
|
17
|
-
self.parent_node = parent_node
|
18
|
-
self.schema_errors = { schema_errors_key => [] }
|
19
|
-
self.validation_errors = { schema_errors_key => [] }
|
20
|
-
|
21
|
-
if maybe_criteria_applies?(input)
|
22
|
-
self.value = input
|
23
|
-
else
|
24
|
-
self.value = value_or_default_value(input)
|
25
|
-
self.value = coerce_value(value) unless maybe_criteria_applies?(value)
|
26
|
-
end
|
27
|
-
|
28
|
-
self_without_empty_schema_errors
|
29
|
-
rescue Dry::Types::ConstraintError, Dry::Types::CoercionError => error
|
30
|
-
add_schema_error(error.message)
|
31
|
-
self_without_empty_schema_errors
|
32
|
-
ensure
|
33
|
-
mark_as_applied
|
34
|
-
end
|
35
|
-
|
36
|
-
private
|
37
|
-
|
38
|
-
def resolve_type(name_or_type)
|
39
|
-
root.send(:type_resolver).resolve(type_system, name_or_type)
|
4
|
+
def call
|
5
|
+
apply_on_evaluators
|
6
|
+
return self if maybe_evaluator_applies?
|
7
|
+
|
8
|
+
coerce_input
|
9
|
+
register_as_coerced_when_no_errors
|
10
|
+
run_validations
|
11
|
+
self
|
40
12
|
end
|
41
13
|
end
|
42
14
|
end
|
@@ -1,146 +1,113 @@
|
|
1
1
|
module NxtSchema
|
2
2
|
module Node
|
3
3
|
class Schema < Node::Base
|
4
|
-
def
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
def apply(input, parent_node: self.parent_node, context: nil)
|
10
|
-
self.input = input
|
11
|
-
register_node(context)
|
12
|
-
|
13
|
-
self.parent_node = parent_node
|
14
|
-
self.schema_errors = { schema_errors_key => [] }
|
15
|
-
self.validation_errors = { schema_errors_key => [] }
|
16
|
-
self.value_store = {}
|
17
|
-
self.value = transform_keys(input)
|
18
|
-
|
19
|
-
if maybe_criteria_applies?(value)
|
20
|
-
self.value_store = value
|
21
|
-
else
|
22
|
-
self.value = value_or_default_value(value)
|
23
|
-
|
24
|
-
unless maybe_criteria_applies?(value)
|
25
|
-
self.value = coerce_value(value)
|
26
|
-
|
27
|
-
# TODO: We should not allow additional keys to be present per default?!
|
28
|
-
# TODO: Handle this here
|
4
|
+
def call
|
5
|
+
apply_on_evaluators
|
6
|
+
child_nodes # build nodes here so we can access them even when invalid
|
7
|
+
return self if maybe_evaluator_applies?
|
29
8
|
|
9
|
+
coerce_input
|
10
|
+
return self unless valid?
|
30
11
|
|
12
|
+
flag_missing_keys
|
13
|
+
apply_additional_keys_strategy
|
31
14
|
|
32
|
-
|
33
|
-
|
15
|
+
child_nodes.each do |key, child|
|
16
|
+
current_node = child.call
|
34
17
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
value_store[key] = node.value
|
40
|
-
schema_errors[key] = node.schema_errors
|
41
|
-
validation_errors[key] = node.validation_errors
|
42
|
-
else
|
43
|
-
evaluate_optional_option(node, input, key)
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
self.value_store = coerce_value(value_store)
|
48
|
-
self.value = value_store
|
18
|
+
if !current_node.valid?
|
19
|
+
merge_errors(current_node)
|
20
|
+
else
|
21
|
+
output[key] = current_node.output
|
49
22
|
end
|
50
23
|
end
|
51
24
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
ensure
|
57
|
-
mark_as_applied
|
25
|
+
transform_keys
|
26
|
+
register_as_coerced_when_no_errors
|
27
|
+
run_validations
|
28
|
+
self
|
58
29
|
end
|
59
30
|
|
60
|
-
|
61
|
-
raise_invalid_options_presence_options if options[:presence]
|
31
|
+
delegate :[], to: :child_nodes
|
62
32
|
|
63
|
-
|
64
|
-
end
|
33
|
+
private
|
65
34
|
|
66
|
-
def
|
67
|
-
|
35
|
+
def transform_keys
|
36
|
+
transformer = node.key_transformer
|
37
|
+
return unless transformer && output.respond_to?(:transform_keys!)
|
68
38
|
|
69
|
-
|
39
|
+
output.transform_keys!(&transformer)
|
70
40
|
end
|
71
41
|
|
72
|
-
|
42
|
+
def keys
|
43
|
+
@keys ||= node.sub_nodes.reject { |key, _| optional_and_not_given_key?(key) }.keys
|
44
|
+
end
|
73
45
|
|
74
|
-
def
|
75
|
-
|
76
|
-
|
77
|
-
if optional_option.respond_to?(:call)
|
78
|
-
# Validator is added to the schema node!
|
79
|
-
add_validators(validator(:optional_node, optional_option, key))
|
80
|
-
elsif !optional_option
|
81
|
-
error_message = ErrorMessages.resolve(
|
82
|
-
locale,
|
83
|
-
:required_key_missing,
|
84
|
-
key: key,
|
85
|
-
target: hash
|
86
|
-
)
|
87
|
-
|
88
|
-
add_schema_error(error_message)
|
89
|
-
end
|
46
|
+
def additional_keys
|
47
|
+
@additional_keys ||= input.keys - keys
|
90
48
|
end
|
91
49
|
|
92
|
-
def
|
93
|
-
|
50
|
+
def optional_and_not_given_key?(key)
|
51
|
+
node.sub_nodes[key].optional? && !input.key?(key)
|
52
|
+
end
|
94
53
|
|
95
|
-
|
54
|
+
def additional_keys?
|
55
|
+
additional_keys.any?
|
96
56
|
end
|
97
57
|
|
98
|
-
def
|
99
|
-
@
|
58
|
+
def missing_keys
|
59
|
+
@missing_keys ||= node.sub_nodes.reject { |_, node| node.omnipresent? || node.optional? }.keys - input.keys
|
100
60
|
end
|
101
61
|
|
102
|
-
def
|
103
|
-
return
|
104
|
-
return
|
62
|
+
def apply_additional_keys_strategy
|
63
|
+
return if allow_additional_keys?
|
64
|
+
return unless additional_keys?
|
105
65
|
|
106
66
|
if restrict_additional_keys?
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
keys: additional_keys_from_input,
|
111
|
-
target: input
|
112
|
-
)
|
113
|
-
|
114
|
-
add_schema_error(error_message)
|
115
|
-
|
116
|
-
template_store.keys
|
117
|
-
else
|
118
|
-
raise Errors::InvalidOptionsError, "Invalid option for additional keys: #{additional_keys_strategy}"
|
67
|
+
add_schema_error("Additional keys are not allowed: #{additional_keys}")
|
68
|
+
elsif reject_additional_keys?
|
69
|
+
self.output = output.except(*additional_keys)
|
119
70
|
end
|
120
71
|
end
|
121
72
|
|
122
|
-
def
|
123
|
-
|
124
|
-
end
|
73
|
+
def flag_missing_keys
|
74
|
+
return if missing_keys.empty?
|
125
75
|
|
126
|
-
|
127
|
-
(input&.keys || []) - template_store.keys
|
76
|
+
add_schema_error("The following keys are missing: #{missing_keys}")
|
128
77
|
end
|
129
78
|
|
130
|
-
def
|
131
|
-
additional_keys_strategy
|
79
|
+
def allow_additional_keys?
|
80
|
+
node.additional_keys_strategy == :allow
|
132
81
|
end
|
133
82
|
|
134
|
-
def
|
135
|
-
additional_keys_strategy
|
83
|
+
def reject_additional_keys?
|
84
|
+
node.additional_keys_strategy == :reject
|
136
85
|
end
|
137
86
|
|
138
87
|
def restrict_additional_keys?
|
139
|
-
additional_keys_strategy
|
88
|
+
node.additional_keys_strategy == :restrict
|
89
|
+
end
|
90
|
+
|
91
|
+
def child_nodes
|
92
|
+
@child_nodes ||= begin
|
93
|
+
keys.inject({}) do |acc, key|
|
94
|
+
child_node = build_child_node(key)
|
95
|
+
acc[key] = child_node if child_node.present?
|
96
|
+
acc
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def build_child_node(key)
|
102
|
+
sub_node = node.sub_nodes[key]
|
103
|
+
return unless sub_node.present?
|
104
|
+
|
105
|
+
value = input_has_key?(input, key) ? input[key] : Undefined.new
|
106
|
+
sub_node.build_node(input: value, context: context, parent: self)
|
140
107
|
end
|
141
108
|
|
142
|
-
def
|
143
|
-
|
109
|
+
def input_has_key?(input, key)
|
110
|
+
input.respond_to?(:key?) && input.key?(key)
|
144
111
|
end
|
145
112
|
end
|
146
113
|
end
|