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
@@ -1,76 +1,21 @@
|
|
1
1
|
module NxtSchema
|
2
2
|
module Node
|
3
3
|
class Collection < Node::Base
|
4
|
-
|
5
|
-
@template_store = TemplateStore.new
|
6
|
-
super
|
7
|
-
end
|
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 = 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
|
-
current_node_store = {}
|
4
|
+
include HasSubNodes
|
28
5
|
|
29
|
-
|
30
|
-
# message = ErrorMessages.resolve(locale, :emptiness, value: value)
|
31
|
-
# add_error(message)
|
32
|
-
# end
|
6
|
+
DEFAULT_TYPE = NxtSchema::Types::Strict::Array
|
33
7
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
template_store.each do |node_name, node|
|
39
|
-
current_node = node.dup
|
40
|
-
current_node_store[node_name] = current_node
|
41
|
-
current_node.apply(item, parent_node: self, context: context)
|
42
|
-
value_store[index] = current_node.value
|
43
|
-
|
44
|
-
unless current_node.schema_errors?
|
45
|
-
current_node_store.each do |node_name, node|
|
46
|
-
node.schema_errors = { }
|
47
|
-
node.validation_errors = { }
|
48
|
-
item_schema_errors = schema_errors[index][node_name] = node.schema_errors
|
49
|
-
validation_errors[index][node_name] = node.validation_errors
|
50
|
-
end
|
51
|
-
|
52
|
-
break
|
53
|
-
else
|
54
|
-
schema_errors[index][node_name] = current_node.schema_errors
|
55
|
-
validation_errors[index][node_name] = current_node.validation_errors
|
56
|
-
end
|
57
|
-
end
|
8
|
+
def initialize(name:, type: DEFAULT_TYPE, parent_node:, **options, &block)
|
9
|
+
super
|
10
|
+
end
|
58
11
|
|
59
|
-
|
60
|
-
end
|
12
|
+
private
|
61
13
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
end
|
66
|
-
end
|
14
|
+
def add_sub_node(node)
|
15
|
+
# TODO: Spec that this raises
|
16
|
+
raise ArgumentError, "It's not possible to define multiple nodes within a collection" unless sub_nodes.empty?
|
67
17
|
|
68
|
-
|
69
|
-
rescue Dry::Types::ConstraintError, Dry::Types::CoercionError => error
|
70
|
-
add_schema_error(error.message)
|
71
|
-
self_without_empty_schema_errors
|
72
|
-
ensure
|
73
|
-
mark_as_applied
|
18
|
+
super
|
74
19
|
end
|
75
20
|
end
|
76
21
|
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module NxtSchema
|
2
|
+
module Node
|
3
|
+
module HasSubNodes
|
4
|
+
def collection(name, type = NxtSchema::Node::Collection::DEFAULT_TYPE, **options, &block)
|
5
|
+
node = NxtSchema::Node::Collection.new(
|
6
|
+
name: name,
|
7
|
+
type: type,
|
8
|
+
parent_node: self,
|
9
|
+
**options,
|
10
|
+
&block
|
11
|
+
)
|
12
|
+
|
13
|
+
add_sub_node(node)
|
14
|
+
end
|
15
|
+
|
16
|
+
alias nodes collection
|
17
|
+
|
18
|
+
def schema(name, type = NxtSchema::Node::Schema::DEFAULT_TYPE, **options, &block)
|
19
|
+
node = NxtSchema::Node::Schema.new(
|
20
|
+
name: name,
|
21
|
+
type: type,
|
22
|
+
parent_node: self,
|
23
|
+
**options,
|
24
|
+
&block
|
25
|
+
)
|
26
|
+
|
27
|
+
add_sub_node(node)
|
28
|
+
end
|
29
|
+
|
30
|
+
def any_of(name, **options, &block)
|
31
|
+
node = NxtSchema::Node::AnyOf.new(
|
32
|
+
name: name,
|
33
|
+
parent_node: self,
|
34
|
+
**options,
|
35
|
+
&block
|
36
|
+
)
|
37
|
+
|
38
|
+
add_sub_node(node)
|
39
|
+
end
|
40
|
+
|
41
|
+
def node(name, node_or_type_of_node, **options, &block)
|
42
|
+
node = if node_or_type_of_node.is_a?(NxtSchema::Node::Base)
|
43
|
+
raise ArgumentError, "Can't provide a block along with a node" if block.present?
|
44
|
+
|
45
|
+
node_or_type_of_node.class.new(
|
46
|
+
name: name,
|
47
|
+
type: node_or_type_of_node.type,
|
48
|
+
parent_node: self,
|
49
|
+
**node_or_type_of_node.options.merge(options), # Does this make sense to merge options here?
|
50
|
+
&node_or_type_of_node.configuration
|
51
|
+
)
|
52
|
+
else
|
53
|
+
NxtSchema::Node::Leaf.new(
|
54
|
+
name: name,
|
55
|
+
type: node_or_type_of_node,
|
56
|
+
parent_node: self,
|
57
|
+
**options,
|
58
|
+
&block
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
add_sub_node(node)
|
63
|
+
end
|
64
|
+
|
65
|
+
alias required node
|
66
|
+
|
67
|
+
def add_sub_node(node)
|
68
|
+
sub_nodes.add(node)
|
69
|
+
node
|
70
|
+
end
|
71
|
+
|
72
|
+
def sub_nodes
|
73
|
+
@sub_nodes ||= Node::SubNodes.new
|
74
|
+
end
|
75
|
+
|
76
|
+
def [](key)
|
77
|
+
sub_nodes[key]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/nxt_schema/node/leaf.rb
CHANGED
@@ -1,43 +1,13 @@
|
|
1
1
|
module NxtSchema
|
2
2
|
module Node
|
3
3
|
class Leaf < Node::Base
|
4
|
-
def initialize(name:, type
|
4
|
+
def initialize(name:, type: :String, parent_node:, **options, &block)
|
5
5
|
super
|
6
|
-
@type = resolve_type(type)
|
7
6
|
end
|
8
7
|
|
9
8
|
def leaf?
|
10
9
|
true
|
11
10
|
end
|
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)
|
40
|
-
end
|
41
11
|
end
|
42
12
|
end
|
43
13
|
end
|
@@ -1,23 +1,28 @@
|
|
1
1
|
module NxtSchema
|
2
2
|
module Node
|
3
3
|
class MaybeEvaluator
|
4
|
-
def initialize(
|
5
|
-
@node = node
|
6
|
-
@evaluator = evaluator
|
4
|
+
def initialize(value:)
|
7
5
|
@value = value
|
8
6
|
end
|
9
7
|
|
10
|
-
|
8
|
+
def call(target = nil, *args)
|
9
|
+
evaluator = evaluator(target, *args)
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
elsif value.is_a?(Symbol) && value.respond_to?(evaluator)
|
16
|
-
Callable.new(evaluator).bind(value).call(node, value)
|
11
|
+
if evaluator.value?
|
12
|
+
# When a value was given we check if this equals to the input
|
13
|
+
evaluator.call == target
|
17
14
|
else
|
18
|
-
|
15
|
+
evaluator.call
|
19
16
|
end
|
20
17
|
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def evaluator(target, *args)
|
22
|
+
Callable.new(value, target, *args)
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :value
|
21
26
|
end
|
22
27
|
end
|
23
28
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module NxtSchema
|
2
|
+
module Node
|
3
|
+
class OnEvaluator
|
4
|
+
def initialize(condition:, value:)
|
5
|
+
@condition = condition
|
6
|
+
@value = value
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(target = nil, *args, &block)
|
10
|
+
return unless condition_applies?(target, *args)
|
11
|
+
|
12
|
+
result = Callable.new(value, target, *args).call
|
13
|
+
block.yield(result)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def condition_applies?(target, *args)
|
19
|
+
Callable.new(condition, target, *args).call
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :condition, :value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -1,146 +1,20 @@
|
|
1
1
|
module NxtSchema
|
2
2
|
module Node
|
3
3
|
class Schema < Node::Base
|
4
|
-
|
5
|
-
@template_store = TemplateStore.new
|
6
|
-
super
|
7
|
-
end
|
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
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
sanitized_keys.each do |key|
|
33
|
-
node = template_store[key]
|
34
|
-
|
35
|
-
if allowed_additional_key?(key)
|
36
|
-
value_store[key] = input[key]
|
37
|
-
elsif node.presence? || input.key?(key)
|
38
|
-
node.apply(input[key], parent_node: self, context: context).schema_errors?
|
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
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
self_without_empty_schema_errors
|
53
|
-
rescue Dry::Types::ConstraintError, Dry::Types::CoercionError => error
|
54
|
-
add_schema_error(error.message)
|
55
|
-
self_without_empty_schema_errors
|
56
|
-
ensure
|
57
|
-
mark_as_applied
|
58
|
-
end
|
59
|
-
|
60
|
-
def optional(name, type, **options, &block)
|
61
|
-
raise_invalid_options_presence_options if options[:presence]
|
62
|
-
|
63
|
-
node(name, type, options.merge(optional: true), &block)
|
64
|
-
end
|
65
|
-
|
66
|
-
def present(name, type, **options, &block)
|
67
|
-
raise_invalid_options_presence_options if options[:optional]
|
68
|
-
|
69
|
-
node(name, type, options.merge(presence: true), &block)
|
70
|
-
end
|
71
|
-
|
72
|
-
private
|
4
|
+
include HasSubNodes
|
73
5
|
|
74
|
-
|
75
|
-
optional_option = node.options[:optional]
|
6
|
+
DEFAULT_TYPE = NxtSchema::Types::Strict::Hash
|
76
7
|
|
77
|
-
|
78
|
-
|
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
|
90
|
-
end
|
91
|
-
|
92
|
-
def transform_keys(hash)
|
93
|
-
return hash unless key_transformer && hash.respond_to?(:transform_keys!)
|
94
|
-
|
95
|
-
hash.transform_keys! { |key| Callable.new(key_transformer).bind(key).call(key) }
|
96
|
-
end
|
97
|
-
|
98
|
-
def key_transformer
|
99
|
-
@key_transformer ||= root.options.fetch(:transform_keys) { false }
|
100
|
-
end
|
101
|
-
|
102
|
-
def sanitized_keys
|
103
|
-
return template_store.keys if additional_keys_from_input.empty? || ignore_additional_keys?
|
104
|
-
return template_store.keys + additional_keys_from_input if additional_keys_allowed?
|
105
|
-
|
106
|
-
if restrict_additional_keys?
|
107
|
-
error_message = ErrorMessages.resolve(
|
108
|
-
locale,
|
109
|
-
:additional_keys_detected,
|
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}"
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
def allowed_additional_key?(key)
|
123
|
-
additional_keys_from_input.include?(key)
|
124
|
-
end
|
125
|
-
|
126
|
-
def additional_keys_from_input
|
127
|
-
(input&.keys || []) - template_store.keys
|
128
|
-
end
|
129
|
-
|
130
|
-
def additional_keys_allowed?
|
131
|
-
additional_keys_strategy.to_s == 'allow'
|
132
|
-
end
|
133
|
-
|
134
|
-
def ignore_additional_keys?
|
135
|
-
additional_keys_strategy.to_s == 'ignore'
|
8
|
+
def initialize(name:, type: DEFAULT_TYPE, parent_node:, **options, &block)
|
9
|
+
super
|
136
10
|
end
|
137
11
|
|
138
|
-
def
|
139
|
-
|
12
|
+
def optional(name, node_or_type_of_node, **options, &block)
|
13
|
+
node(name, node_or_type_of_node, **options.merge(optional: true), &block)
|
140
14
|
end
|
141
15
|
|
142
|
-
def
|
143
|
-
|
16
|
+
def omnipresent(name, node_or_type_of_node, **options, &block)
|
17
|
+
node(name, node_or_type_of_node, **options.merge(omnipresent: true), &block)
|
144
18
|
end
|
145
19
|
end
|
146
20
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module NxtSchema
|
2
|
+
module Node
|
3
|
+
class SubNodes < ::Hash
|
4
|
+
def initialize
|
5
|
+
super
|
6
|
+
transform_keys { |k| k.to_sym }
|
7
|
+
end
|
8
|
+
|
9
|
+
def add(node)
|
10
|
+
node_name = node.name
|
11
|
+
ensure_node_name_free(node_name)
|
12
|
+
self[node_name] = node
|
13
|
+
end
|
14
|
+
|
15
|
+
def ensure_node_name_free(name)
|
16
|
+
return unless key?(name)
|
17
|
+
|
18
|
+
raise KeyError, "Node with name '#{name}' already exists! Node names must be unique!"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|