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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -0
  3. data/Gemfile +0 -1
  4. data/Gemfile.lock +32 -29
  5. data/README.md +186 -116
  6. data/lib/nxt_schema.rb +56 -49
  7. data/lib/nxt_schema/{node.rb → application.rb} +1 -1
  8. data/lib/nxt_schema/application/any_of.rb +40 -0
  9. data/lib/nxt_schema/application/base.rb +116 -0
  10. data/lib/nxt_schema/application/collection.rb +57 -0
  11. data/lib/nxt_schema/application/error_store.rb +57 -0
  12. data/lib/nxt_schema/application/errors/schema_error.rb +15 -0
  13. data/lib/nxt_schema/application/errors/validation_error.rb +15 -0
  14. data/lib/nxt_schema/application/leaf.rb +15 -0
  15. data/lib/nxt_schema/application/schema.rb +114 -0
  16. data/lib/nxt_schema/callable.rb +21 -55
  17. data/lib/nxt_schema/dsl.rb +41 -31
  18. data/lib/nxt_schema/error.rb +4 -0
  19. data/lib/nxt_schema/errors/invalid.rb +16 -0
  20. data/lib/nxt_schema/errors/{error.rb → invalid_options.rb} +1 -2
  21. data/lib/nxt_schema/missing_input.rb +9 -0
  22. data/lib/nxt_schema/node/any_of.rb +51 -0
  23. data/lib/nxt_schema/node/base.rb +135 -233
  24. data/lib/nxt_schema/node/collection.rb +10 -65
  25. data/lib/nxt_schema/node/has_sub_nodes.rb +81 -0
  26. data/lib/nxt_schema/node/leaf.rb +1 -31
  27. data/lib/nxt_schema/node/maybe_evaluator.rb +15 -10
  28. data/lib/nxt_schema/node/on_evaluator.rb +25 -0
  29. data/lib/nxt_schema/node/schema.rb +8 -134
  30. data/lib/nxt_schema/node/sub_nodes.rb +22 -0
  31. data/lib/nxt_schema/node/type_system_resolver.rb +22 -0
  32. data/lib/nxt_schema/types.rb +1 -1
  33. data/lib/nxt_schema/validators/attribute.rb +3 -3
  34. data/lib/nxt_schema/validators/{equality.rb → equal_to.rb} +5 -5
  35. data/lib/nxt_schema/validators/error_messages.rb +42 -0
  36. data/lib/nxt_schema/{error_messages → validators/error_messages}/en.yaml +3 -3
  37. data/lib/nxt_schema/validators/{excluded.rb → excluded_in.rb} +4 -4
  38. data/lib/nxt_schema/validators/excludes.rb +3 -3
  39. data/lib/nxt_schema/validators/greater_than.rb +3 -3
  40. data/lib/nxt_schema/validators/greater_than_or_equal.rb +3 -3
  41. data/lib/nxt_schema/validators/{included.rb → included_in.rb} +4 -4
  42. data/lib/nxt_schema/validators/includes.rb +3 -3
  43. data/lib/nxt_schema/validators/less_than.rb +3 -3
  44. data/lib/nxt_schema/validators/less_than_or_equal.rb +3 -3
  45. data/lib/nxt_schema/validators/optional_node.rb +13 -8
  46. data/lib/nxt_schema/validators/pattern.rb +3 -3
  47. data/lib/nxt_schema/validators/query.rb +4 -4
  48. data/lib/nxt_schema/validators/registry.rb +1 -7
  49. data/lib/nxt_schema/{node → validators}/validate_with_proxy.rb +8 -8
  50. data/lib/nxt_schema/validators/validator.rb +2 -2
  51. data/lib/nxt_schema/version.rb +1 -1
  52. data/nxt_schema.gemspec +1 -0
  53. metadata +42 -22
  54. data/lib/nxt_schema/callable_or_value.rb +0 -72
  55. data/lib/nxt_schema/error_messages.rb +0 -40
  56. data/lib/nxt_schema/errors.rb +0 -4
  57. data/lib/nxt_schema/errors/invalid_options_error.rb +0 -5
  58. data/lib/nxt_schema/errors/schema_not_applied_error.rb +0 -5
  59. data/lib/nxt_schema/node/constructor.rb +0 -9
  60. data/lib/nxt_schema/node/default_value_evaluator.rb +0 -20
  61. data/lib/nxt_schema/node/error.rb +0 -13
  62. data/lib/nxt_schema/node/has_subnodes.rb +0 -97
  63. data/lib/nxt_schema/node/template_store.rb +0 -15
  64. data/lib/nxt_schema/registry.rb +0 -85
  65. 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
- def initialize(name:, type: NxtSchema::Types::Strict::Array, parent_node:, **options, &block)
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
- # if value.empty?
30
- # message = ErrorMessages.resolve(locale, :emptiness, value: value)
31
- # add_error(message)
32
- # end
6
+ DEFAULT_TYPE = NxtSchema::Types::Strict::Array
33
7
 
34
- value.each_with_index do |item, index|
35
- item_schema_errors = schema_errors[index] ||= { schema_errors_key => [] }
36
- validation_errors[index] ||= { schema_errors_key => [] }
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
- item_schema_errors.reject! { |_, v| v.empty? }
60
- end
12
+ private
61
13
 
62
- # Once we collected all values ensure type by casting again
63
- self.value_store = coerce_value(value_store)
64
- self.value = value_store
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
- self_without_empty_schema_errors
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
@@ -1,43 +1,13 @@
1
1
  module NxtSchema
2
2
  module Node
3
3
  class Leaf < Node::Base
4
- def initialize(name:, type:, parent_node:, **options, &block)
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(node, evaluator, value)
5
- @node = node
6
- @evaluator = evaluator
4
+ def initialize(value:)
7
5
  @value = value
8
6
  end
9
7
 
10
- attr_reader :node, :evaluator, :value
8
+ def call(target = nil, *args)
9
+ evaluator = evaluator(target, *args)
11
10
 
12
- def call
13
- if evaluator.respond_to?(:call)
14
- Callable.new(evaluator).call(node, value)
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
- value == evaluator
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
- def initialize(name:, type: NxtSchema::Types::Strict::Hash, parent_node:, **options, &block)
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
- def evaluate_optional_option(node, hash, key)
75
- optional_option = node.options[:optional]
6
+ DEFAULT_TYPE = NxtSchema::Types::Strict::Hash
76
7
 
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
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 restrict_additional_keys?
139
- additional_keys_strategy.to_s == 'restrict'
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 raise_invalid_options_presence_options
143
- raise InvalidOptionsError, 'Options :presence and :optional exclude each other'
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