nxt_schema 0.1.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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