nxt_schema 1.0.0 → 1.0.1

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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +3 -3
  3. data/README.md +80 -38
  4. data/lib/nxt_schema.rb +17 -17
  5. data/lib/nxt_schema/dsl.rb +7 -7
  6. data/lib/nxt_schema/{application.rb → node.rb} +1 -1
  7. data/lib/nxt_schema/node/any_of.rb +21 -33
  8. data/lib/nxt_schema/node/base.rb +70 -171
  9. data/lib/nxt_schema/node/collection.rb +43 -8
  10. data/lib/nxt_schema/{application → node}/error_store.rb +5 -5
  11. data/lib/nxt_schema/{application → node}/errors/schema_error.rb +1 -1
  12. data/lib/nxt_schema/{application → node}/errors/validation_error.rb +1 -1
  13. data/lib/nxt_schema/node/leaf.rb +7 -5
  14. data/lib/nxt_schema/node/schema.rb +101 -8
  15. data/lib/nxt_schema/template/any_of.rb +50 -0
  16. data/lib/nxt_schema/template/base.rb +218 -0
  17. data/lib/nxt_schema/template/collection.rb +23 -0
  18. data/lib/nxt_schema/{node → template}/has_sub_nodes.rb +16 -10
  19. data/lib/nxt_schema/template/leaf.rb +13 -0
  20. data/lib/nxt_schema/{node → template}/maybe_evaluator.rb +1 -1
  21. data/lib/nxt_schema/{node → template}/on_evaluator.rb +1 -1
  22. data/lib/nxt_schema/template/schema.rb +22 -0
  23. data/lib/nxt_schema/{node → template}/sub_nodes.rb +1 -1
  24. data/lib/nxt_schema/{node → template}/type_resolver.rb +2 -2
  25. data/lib/nxt_schema/{node → template}/type_system_resolver.rb +1 -1
  26. data/lib/nxt_schema/version.rb +1 -1
  27. metadata +17 -17
  28. data/lib/nxt_schema/application/any_of.rb +0 -40
  29. data/lib/nxt_schema/application/base.rb +0 -116
  30. data/lib/nxt_schema/application/collection.rb +0 -57
  31. data/lib/nxt_schema/application/leaf.rb +0 -15
  32. data/lib/nxt_schema/application/schema.rb +0 -114
@@ -0,0 +1,50 @@
1
+ module NxtSchema
2
+ module Template
3
+ class AnyOf < Base
4
+ include HasSubNodes
5
+
6
+ def initialize(name:, type: nil, parent_node:, **options, &block)
7
+ super
8
+ ensure_sub_nodes_present
9
+ end
10
+
11
+ def collection(name = sub_nodes.count, type = NxtSchema::Template::Collection::DEFAULT_TYPE, **options, &block)
12
+ super
13
+ end
14
+
15
+ def schema(name = sub_nodes.count, type = NxtSchema::Template::Schema::DEFAULT_TYPE, **options, &block)
16
+ super
17
+ end
18
+
19
+ def node(name = sub_nodes.count, node_or_type_of_node = nil, **options, &block)
20
+ super
21
+ end
22
+
23
+ def on(*args)
24
+ raise NotImplementedError
25
+ end
26
+
27
+ def maybe(*args)
28
+ raise NotImplementedError
29
+ end
30
+
31
+ private
32
+
33
+ def resolve_type(name_or_type)
34
+ nil
35
+ end
36
+
37
+ def resolve_optional_option
38
+ return unless options.key?(:optional)
39
+
40
+ raise InvalidOptions, "The optional option is not available for nodes of type #{self.class.name}"
41
+ end
42
+
43
+ def resolve_omnipresent_option
44
+ return unless options.key?(:omnipresent)
45
+
46
+ raise InvalidOptions, "The omnipresent option is not available for nodes of type #{self.class.name}"
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,218 @@
1
+ module NxtSchema
2
+ module Template
3
+ class Base
4
+ def initialize(name:, type:, parent_node:, **options, &block)
5
+ resolve_name(name)
6
+
7
+ @parent_node = parent_node
8
+ @options = options
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 = []
14
+ @validations = []
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
+ end
27
+
28
+ attr_accessor :name,
29
+ :parent_node,
30
+ :options,
31
+ :type,
32
+ :root_node,
33
+ :additional_keys_strategy
34
+
35
+ attr_reader :type_system,
36
+ :path,
37
+ :context,
38
+ :meta,
39
+ :on_evaluators,
40
+ :maybe_evaluators,
41
+ :validations,
42
+ :configuration,
43
+ :key_transformer
44
+
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
47
+ end
48
+
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?
52
+
53
+ raise NxtSchema::Errors::Invalid.new(result)
54
+ end
55
+
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
+ )
64
+ end
65
+
66
+ def root_node?
67
+ @is_root_node
68
+ end
69
+
70
+ def optional?
71
+ @optional
72
+ end
73
+
74
+ def omnipresent?
75
+ @omnipresent
76
+ end
77
+
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)
82
+
83
+ self
84
+ end
85
+
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)
89
+
90
+ self
91
+ end
92
+
93
+ def maybe(value = NxtSchema::MissingInput.new, &block)
94
+ value = missing_input?(value) ? block : value
95
+ maybe_evaluators << MaybeEvaluator.new(value: value)
96
+
97
+ self
98
+ end
99
+
100
+ def validate(key = NxtSchema::MissingInput.new, *args, &block)
101
+ # TODO: This does not really work with all kinds of chaining combinations yet!
102
+
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
116
+
117
+ register_validator(validator)
118
+
119
+ self
120
+ end
121
+
122
+ def validate_with(&block)
123
+ proxy = ->(node) { NxtSchema::Validator::ValidateWithProxy.new(node).validate(&block) }
124
+ register_validator(proxy)
125
+ end
126
+
127
+ private
128
+
129
+ attr_writer :path, :meta, :context, :on_evaluators, :maybe_evaluators
130
+
131
+ def validator(key, *args)
132
+ Validators::REGISTRY.resolve!(key).new(*args).build
133
+ end
134
+
135
+ def register_validator(validator)
136
+ validations << validator
137
+ end
138
+
139
+ def resolve_type(name_or_type)
140
+ @type = root_node.send(:type_resolver).resolve(type_system, name_or_type)
141
+ end
142
+
143
+ def resolve_type_system
144
+ @type_system = TypeSystemResolver.new(node: self).call
145
+ end
146
+
147
+ def type_resolver
148
+ @type_resolver ||= begin
149
+ root_node? ? TypeResolver.new : (raise NoMethodError, 'type_resolver is only available on root node')
150
+ end
151
+ end
152
+
153
+ def application_class
154
+ @application_class ||= "NxtSchema::Node::#{self.class.name.demodulize}".constantize
155
+ end
156
+
157
+ def configure(&block)
158
+ if block.arity == 1
159
+ block.call(self)
160
+ else
161
+ instance_exec(&block)
162
+ end
163
+ end
164
+
165
+ def resolve_additional_keys_strategy
166
+ @additional_keys_strategy = options.fetch(:additional_keys) do
167
+ parent_node&.send(:additional_keys_strategy) || :allow
168
+ end
169
+ end
170
+
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
182
+ else
183
+ @optional = optional
184
+ end
185
+ end
186
+
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
191
+
192
+ @omnipresent = omnipresent
193
+ end
194
+
195
+ def resolve_path
196
+ self.path = root_node? ? name : "#{parent_node.path}.#{name}"
197
+ end
198
+
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
205
+ end
206
+
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
215
+ end
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,23 @@
1
+ module NxtSchema
2
+ module Template
3
+ class Collection < Template::Base
4
+ include HasSubNodes
5
+
6
+ DEFAULT_TYPE = NxtSchema::Types::Strict::Array
7
+
8
+ def initialize(name:, type: DEFAULT_TYPE, parent_node:, **options, &block)
9
+ super
10
+ ensure_sub_nodes_present
11
+ end
12
+
13
+ private
14
+
15
+ def add_sub_node(node)
16
+ # TODO: Spec that this raises
17
+ raise ArgumentError, "It's not possible to define multiple nodes within a collection" unless sub_nodes.empty?
18
+
19
+ super
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,8 +1,8 @@
1
1
  module NxtSchema
2
- module Node
2
+ module Template
3
3
  module HasSubNodes
4
- def collection(name, type = NxtSchema::Node::Collection::DEFAULT_TYPE, **options, &block)
5
- node = NxtSchema::Node::Collection.new(
4
+ def collection(name, type = NxtSchema::Template::Collection::DEFAULT_TYPE, **options, &block)
5
+ node = NxtSchema::Template::Collection.new(
6
6
  name: name,
7
7
  type: type,
8
8
  parent_node: self,
@@ -15,8 +15,8 @@ module NxtSchema
15
15
 
16
16
  alias nodes collection
17
17
 
18
- def schema(name, type = NxtSchema::Node::Schema::DEFAULT_TYPE, **options, &block)
19
- node = NxtSchema::Node::Schema.new(
18
+ def schema(name, type = NxtSchema::Template::Schema::DEFAULT_TYPE, **options, &block)
19
+ node = NxtSchema::Template::Schema.new(
20
20
  name: name,
21
21
  type: type,
22
22
  parent_node: self,
@@ -28,7 +28,7 @@ module NxtSchema
28
28
  end
29
29
 
30
30
  def any_of(name, **options, &block)
31
- node = NxtSchema::Node::AnyOf.new(
31
+ node = NxtSchema::Template::AnyOf.new(
32
32
  name: name,
33
33
  parent_node: self,
34
34
  **options,
@@ -39,18 +39,18 @@ module NxtSchema
39
39
  end
40
40
 
41
41
  def node(name, node_or_type_of_node, **options, &block)
42
- node = if node_or_type_of_node.is_a?(NxtSchema::Node::Base)
42
+ node = if node_or_type_of_node.is_a?(NxtSchema::Template::Base)
43
43
  raise ArgumentError, "Can't provide a block along with a node" if block.present?
44
44
 
45
45
  node_or_type_of_node.class.new(
46
46
  name: name,
47
47
  type: node_or_type_of_node.type,
48
48
  parent_node: self,
49
- **node_or_type_of_node.options.merge(options), # Does this make sense to merge options here?
49
+ **node_or_type_of_node.options.merge(options),
50
50
  &node_or_type_of_node.configuration
51
51
  )
52
52
  else
53
- NxtSchema::Node::Leaf.new(
53
+ NxtSchema::Template::Leaf.new(
54
54
  name: name,
55
55
  type: node_or_type_of_node,
56
56
  parent_node: self,
@@ -70,12 +70,18 @@ module NxtSchema
70
70
  end
71
71
 
72
72
  def sub_nodes
73
- @sub_nodes ||= Node::SubNodes.new
73
+ @sub_nodes ||= Template::SubNodes.new
74
74
  end
75
75
 
76
76
  def [](key)
77
77
  sub_nodes[key]
78
78
  end
79
+
80
+ def ensure_sub_nodes_present
81
+ return if sub_nodes.any?
82
+
83
+ raise NxtSchema::Errors::InvalidOptions, "#{self.class.name} must have sub nodes"
84
+ end
79
85
  end
80
86
  end
81
87
  end
@@ -0,0 +1,13 @@
1
+ module NxtSchema
2
+ module Template
3
+ class Leaf < Template::Base
4
+ def initialize(name:, type: :String, parent_node:, **options, &block)
5
+ super
6
+ end
7
+
8
+ def leaf?
9
+ true
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,5 +1,5 @@
1
1
  module NxtSchema
2
- module Node
2
+ module Template
3
3
  class MaybeEvaluator
4
4
  def initialize(value:)
5
5
  @value = value
@@ -1,5 +1,5 @@
1
1
  module NxtSchema
2
- module Node
2
+ module Template
3
3
  class OnEvaluator
4
4
  def initialize(condition:, value:)
5
5
  @condition = condition
@@ -0,0 +1,22 @@
1
+ module NxtSchema
2
+ module Template
3
+ class Schema < Template::Base
4
+ include HasSubNodes
5
+
6
+ DEFAULT_TYPE = NxtSchema::Types::Strict::Hash
7
+
8
+ def initialize(name:, type: DEFAULT_TYPE, parent_node:, **options, &block)
9
+ super
10
+ ensure_sub_nodes_present
11
+ end
12
+
13
+ def optional(name, node_or_type_of_node, **options, &block)
14
+ node(name, node_or_type_of_node, **options.merge(optional: true), &block)
15
+ end
16
+
17
+ def omnipresent(name, node_or_type_of_node, **options, &block)
18
+ node(name, node_or_type_of_node, **options.merge(omnipresent: true), &block)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,5 +1,5 @@
1
1
  module NxtSchema
2
- module Node
2
+ module Template
3
3
  class SubNodes < ::Hash
4
4
  def initialize
5
5
  super
@@ -1,5 +1,5 @@
1
1
  module NxtSchema
2
- module Node
2
+ module Template
3
3
  class TypeResolver
4
4
  def resolve(type_system, type)
5
5
  @resolve ||= {}
@@ -21,4 +21,4 @@ module NxtSchema
21
21
  end
22
22
  end
23
23
  end
24
- end
24
+ end
@@ -1,5 +1,5 @@
1
1
  module NxtSchema
2
- module Node
2
+ module Template
3
3
  class TypeSystemResolver
4
4
  include NxtInit
5
5
  attr_init :node