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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -0
  3. data/Gemfile +0 -1
  4. data/Gemfile.lock +40 -42
  5. data/README.md +267 -121
  6. data/lib/nxt_schema.rb +60 -51
  7. data/lib/nxt_schema/callable.rb +21 -55
  8. data/lib/nxt_schema/dsl.rb +41 -31
  9. data/lib/nxt_schema/error.rb +4 -0
  10. data/lib/nxt_schema/errors/{error.rb → coercion_error.rb} +1 -2
  11. data/lib/nxt_schema/errors/invalid.rb +16 -0
  12. data/lib/nxt_schema/errors/invalid_options.rb +6 -0
  13. data/lib/nxt_schema/node/any_of.rb +39 -0
  14. data/lib/nxt_schema/node/base.rb +66 -267
  15. data/lib/nxt_schema/node/collection.rb +40 -56
  16. data/lib/nxt_schema/node/error_store.rb +41 -0
  17. data/lib/nxt_schema/node/errors/schema_error.rb +15 -0
  18. data/lib/nxt_schema/node/errors/validation_error.rb +15 -0
  19. data/lib/nxt_schema/node/leaf.rb +8 -36
  20. data/lib/nxt_schema/node/schema.rb +70 -103
  21. data/lib/nxt_schema/registry.rb +12 -74
  22. data/lib/nxt_schema/registry/proxy.rb +21 -0
  23. data/lib/nxt_schema/template/any_of.rb +50 -0
  24. data/lib/nxt_schema/template/base.rb +220 -0
  25. data/lib/nxt_schema/template/collection.rb +23 -0
  26. data/lib/nxt_schema/template/has_sub_nodes.rb +87 -0
  27. data/lib/nxt_schema/template/leaf.rb +13 -0
  28. data/lib/nxt_schema/template/maybe_evaluator.rb +28 -0
  29. data/lib/nxt_schema/template/on_evaluator.rb +25 -0
  30. data/lib/nxt_schema/template/schema.rb +22 -0
  31. data/lib/nxt_schema/template/sub_nodes.rb +22 -0
  32. data/lib/nxt_schema/template/type_resolver.rb +39 -0
  33. data/lib/nxt_schema/template/type_system_resolver.rb +22 -0
  34. data/lib/nxt_schema/types.rb +7 -4
  35. data/lib/nxt_schema/undefined.rb +4 -2
  36. data/lib/nxt_schema/validators/{equality.rb → equal_to.rb} +2 -2
  37. data/lib/nxt_schema/validators/error_messages.rb +42 -0
  38. data/lib/nxt_schema/{error_messages → validators/error_messages}/en.yaml +6 -5
  39. data/lib/nxt_schema/validators/{excluded.rb → excluded_in.rb} +1 -1
  40. data/lib/nxt_schema/validators/{included.rb → included_in.rb} +1 -1
  41. data/lib/nxt_schema/validators/includes.rb +1 -1
  42. data/lib/nxt_schema/validators/optional_node.rb +11 -6
  43. data/lib/nxt_schema/validators/registry.rb +1 -7
  44. data/lib/nxt_schema/{node → validators}/validate_with_proxy.rb +3 -3
  45. data/lib/nxt_schema/validators/validator.rb +2 -2
  46. data/lib/nxt_schema/version.rb +1 -1
  47. data/nxt_schema.gemspec +1 -0
  48. metadata +44 -21
  49. data/lib/nxt_schema/callable_or_value.rb +0 -72
  50. data/lib/nxt_schema/error_messages.rb +0 -40
  51. data/lib/nxt_schema/errors.rb +0 -4
  52. data/lib/nxt_schema/errors/invalid_options_error.rb +0 -5
  53. data/lib/nxt_schema/errors/schema_not_applied_error.rb +0 -5
  54. data/lib/nxt_schema/node/constructor.rb +0 -9
  55. data/lib/nxt_schema/node/default_value_evaluator.rb +0 -20
  56. data/lib/nxt_schema/node/error.rb +0 -13
  57. data/lib/nxt_schema/node/has_subnodes.rb +0 -97
  58. data/lib/nxt_schema/node/maybe_evaluator.rb +0 -23
  59. data/lib/nxt_schema/node/template_store.rb +0 -15
  60. data/lib/nxt_schema/node/type_resolver.rb +0 -24
data/lib/nxt_schema.rb CHANGED
@@ -1,69 +1,78 @@
1
- require "nxt_schema/version"
2
- require "pry"
3
- require "active_support/all"
1
+ require 'nxt_schema/version'
2
+ require 'active_support/all'
4
3
  require 'dry-types'
5
4
  require 'nxt_registry'
5
+ require 'nxt_init'
6
6
  require 'yaml'
7
7
 
8
- require "nxt_schema/types"
9
- require "nxt_schema/undefined"
10
- require "nxt_schema/registry"
11
- require "nxt_schema/callable"
12
- require "nxt_schema/callable_or_value"
13
- require "nxt_schema/validators/registry"
14
- require "nxt_schema/node/validate_with_proxy"
8
+ require_relative 'nxt_schema/types'
9
+ require_relative 'nxt_schema/callable'
10
+ require_relative 'nxt_schema/node'
11
+ require_relative 'nxt_schema/undefined'
12
+ require_relative 'nxt_schema/error'
13
+ require_relative 'nxt_schema/errors/invalid'
14
+ require_relative 'nxt_schema/errors/invalid_options'
15
+ require_relative 'nxt_schema/errors/coercion_error'
15
16
 
16
- require "nxt_schema/errors"
17
- require "nxt_schema/errors/error"
18
- require "nxt_schema/errors/schema_not_applied_error"
19
- require "nxt_schema/errors/invalid_options_error"
17
+ require_relative 'nxt_schema/validators/registry'
18
+ require_relative 'nxt_schema/validators/validate_with_proxy'
19
+ require_relative 'nxt_schema/validators/error_messages'
20
+ require_relative 'nxt_schema/validators/validator'
21
+ require_relative 'nxt_schema/validators/attribute'
22
+ require_relative 'nxt_schema/validators/equal_to'
23
+ require_relative 'nxt_schema/validators/optional_node'
24
+ require_relative 'nxt_schema/validators/greater_than'
25
+ require_relative 'nxt_schema/validators/greater_than_or_equal'
26
+ require_relative 'nxt_schema/validators/less_than'
27
+ require_relative 'nxt_schema/validators/less_than_or_equal'
28
+ require_relative 'nxt_schema/validators/pattern'
29
+ require_relative 'nxt_schema/validators/included_in'
30
+ require_relative 'nxt_schema/validators/includes'
31
+ require_relative 'nxt_schema/validators/excluded_in'
32
+ require_relative 'nxt_schema/validators/excludes'
33
+ require_relative 'nxt_schema/validators/query'
20
34
 
21
- require "nxt_schema/error_messages"
22
- require "nxt_schema/validators/validator"
23
- require "nxt_schema/validators/attribute"
24
- require "nxt_schema/validators/equality"
25
- require "nxt_schema/validators/optional_node"
26
- require "nxt_schema/validators/greater_than"
27
- require "nxt_schema/validators/greater_than_or_equal"
28
- require "nxt_schema/validators/less_than"
29
- require "nxt_schema/validators/less_than_or_equal"
30
- require "nxt_schema/validators/pattern"
31
- require "nxt_schema/validators/included"
32
- require "nxt_schema/validators/includes"
33
- require "nxt_schema/validators/excluded"
34
- require "nxt_schema/validators/excludes"
35
- require "nxt_schema/validators/query"
35
+ require_relative 'nxt_schema/template/on_evaluator'
36
+ require_relative 'nxt_schema/template/maybe_evaluator'
37
+ require_relative 'nxt_schema/template/type_resolver'
38
+ require_relative 'nxt_schema/template/type_system_resolver'
39
+ require_relative 'nxt_schema/template/base'
40
+ require_relative 'nxt_schema/template/sub_nodes'
41
+ require_relative 'nxt_schema/template/has_sub_nodes'
42
+ require_relative 'nxt_schema/template/any_of'
43
+ require_relative 'nxt_schema/template/collection'
44
+ require_relative 'nxt_schema/template/schema'
45
+ require_relative 'nxt_schema/template/leaf'
36
46
 
37
- require "nxt_schema/node"
38
- require "nxt_schema/node/type_resolver"
39
- require "nxt_schema/node/maybe_evaluator"
40
- require "nxt_schema/node/default_value_evaluator"
41
- require "nxt_schema/node/base"
42
- require "nxt_schema/node/error"
43
- require "nxt_schema/node/has_subnodes"
44
- require "nxt_schema/node/template_store"
45
- require "nxt_schema/node/schema"
46
- require "nxt_schema/node/collection"
47
- require "nxt_schema/node/leaf"
48
- require "nxt_schema/dsl"
47
+ require_relative 'nxt_schema/node/errors/schema_error'
48
+ require_relative 'nxt_schema/node/errors/validation_error'
49
+ require_relative 'nxt_schema/node/error_store'
50
+ require_relative 'nxt_schema/node/base'
51
+ require_relative 'nxt_schema/node/any_of'
52
+ require_relative 'nxt_schema/node/leaf'
53
+ require_relative 'nxt_schema/node/collection'
54
+ require_relative 'nxt_schema/node/schema'
55
+ require_relative 'nxt_schema/dsl'
56
+ require_relative 'nxt_schema/registry/proxy'
57
+ require_relative 'nxt_schema/registry'
49
58
 
50
59
  module NxtSchema
51
- def register_validator(validator, *keys)
52
- keys.each do |key|
53
- NxtSchema::Validators::Registry::VALIDATORS.register(key, validator)
54
- end
60
+ extend Dsl
61
+
62
+ def register_error_messages(*paths)
63
+ Validators::ErrorMessages.load(paths)
55
64
  end
56
65
 
57
- def register_type(key, type)
58
- NxtSchema::Types.const_set(key.to_s, type)
66
+ def register_validator(validator, *keys)
67
+ keys.each { |key| NxtSchema::Validators::REGISTRY.register(key, validator) }
59
68
  end
60
69
 
61
- def register_error_messages(*paths)
62
- ErrorMessages.load(paths)
70
+ def register_type(key, type)
71
+ NxtSchema::Types.registry(:types).register(key, type)
63
72
  end
64
73
 
65
74
  # Load default messages
66
- ErrorMessages.load
75
+ Validators::ErrorMessages.load
67
76
 
68
- module_function :register_validator, :register_type, :register_error_messages
77
+ module_function :register_error_messages, :register_validator, :register_type
69
78
  end
@@ -1,74 +1,40 @@
1
1
  module NxtSchema
2
2
  class Callable
3
- def initialize(callee)
4
- @callee = callee
5
-
6
- if callee.is_a?(Symbol)
7
- self.type = :method
8
- elsif callee.respond_to?(:call)
9
- self.type = :proc
10
- self.context = callee.binding
11
- else
12
- raise ArgumentError, "Callee is nor symbol nor a proc: #{callee}"
13
- end
14
- end
15
-
16
- def bind!(execution_context)
17
- self.context = execution_context
18
- ensure_context_not_missing
19
- self
3
+ def initialize(callable, target = nil, *args)
4
+ @callable = callable
5
+ @target = target
6
+ @args = args
20
7
  end
21
8
 
22
- def bind(execution_context = nil)
23
- return self if context
9
+ def call
10
+ return callable if value?
11
+ return callable.call(*args_from_arity) if proc?
24
12
 
25
- self.context = execution_context
26
- ensure_context_not_missing
27
- self
13
+ target.send(callable, *args_from_arity)
28
14
  end
29
15
 
30
- # NOTE: Currently we only allow arguments! Not keyword args or **options
31
- # If we would allow **options and we would pass a hash as the only argument it would
32
- # automatically be parsed as the options!
33
- def call(*args)
34
- ensure_context_not_missing
35
-
36
- args = args.take(arity)
16
+ def method?
17
+ @method ||= callable.class.in?([Symbol, String]) && target.respond_to?(callable)
18
+ end
37
19
 
38
- if method?
39
- context.send(callee, *args)
40
- else
41
- context.instance_exec(*args, &callee)
42
- end
20
+ def proc?
21
+ @proc ||= callable.respond_to?(:call)
43
22
  end
44
23
 
45
- def arity
46
- if proc?
47
- callee.arity
48
- elsif method?
49
- method = context.send(:method, callee)
50
- method.arity
51
- else
52
- raise ArgumentError, "Can't resolve arity from #{callee}"
53
- end
24
+ def value?
25
+ !method? && !proc?
54
26
  end
55
27
 
56
28
  private
57
29
 
58
- def proc?
59
- type == :proc
60
- end
30
+ attr_reader :callable, :target, :args
61
31
 
62
- def method?
63
- type == :method
32
+ def arity
33
+ proc? ? callable.arity : 0
64
34
  end
65
35
 
66
- def ensure_context_not_missing
67
- return if context
68
-
69
- raise ArgumentError, "Missing context: #{context}"
36
+ def args_from_arity
37
+ @args_from_arity ||= ([target] + args).take(arity)
70
38
  end
71
-
72
- attr_accessor :context, :callee, :type
73
39
  end
74
- end
40
+ end
@@ -1,38 +1,48 @@
1
1
  module NxtSchema
2
- def schema(name = :root, **options, &block)
3
- Node::Schema.new(name: name, parent_node: nil, **options, &block)
4
- end
2
+ module Dsl
3
+ DEFAULT_OPTIONS = { type_system: NxtSchema::Types }.freeze
5
4
 
6
- def collection(name = :roots, **options, &block)
7
- Node::Collection.new(name: name, parent_node: nil, **options, &block)
8
- end
5
+ def collection(name = :root, type: NxtSchema::Template::Collection::DEFAULT_TYPE, **options, &block)
6
+ NxtSchema::Template::Collection.new(
7
+ name: name,
8
+ type: type,
9
+ parent_node: nil,
10
+ **DEFAULT_OPTIONS.merge(options),
11
+ &block
12
+ )
13
+ end
9
14
 
10
- def params(name = :root, **options, &block)
11
- Node::Schema.new(
12
- name: name,
13
- parent_node: nil,
14
- **options.merge(
15
- type_system: NxtSchema::Types::Params,
16
- ).reverse_merge(transform_keys: :to_sym),
17
- &block
18
- )
19
- end
15
+ alias nodes collection
20
16
 
21
- def json(name = :root, **options, &block)
22
- Node::Schema.new(
23
- name: name,
24
- parent_node: nil,
25
- **options.merge(
26
- type_system: NxtSchema::Types::JSON,
27
- ).reverse_merge(transform_keys: :to_sym),
28
- &block
29
- )
30
- end
17
+ def schema(name = :roots, type: NxtSchema::Template::Schema::DEFAULT_TYPE, **options, &block)
18
+ NxtSchema::Template::Schema.new(
19
+ name: name,
20
+ type: type,
21
+ parent_node: nil,
22
+ **DEFAULT_OPTIONS.merge(options),
23
+ &block
24
+ )
25
+ end
26
+
27
+ def any_of(name = :roots, **options, &block)
28
+ NxtSchema::Template::AnyOf.new(
29
+ name: name,
30
+ parent_node: nil,
31
+ **DEFAULT_OPTIONS.merge(options),
32
+ &block
33
+ )
34
+ end
31
35
 
32
- alias_method :node, :schema
33
- alias_method :root, :schema
34
- alias_method :nodes, :collection
35
- alias_method :roots, :collection
36
+ # schema root with NxtSchema::Types::Params type system
36
37
 
37
- module_function :root, :roots, :node, :nodes, :collection, :schema, :params
38
+ def params(name = :params, type: NxtSchema::Template::Schema::DEFAULT_TYPE, **options, &block)
39
+ NxtSchema::Template::Schema.new(
40
+ name: name,
41
+ type: type,
42
+ parent_node: nil,
43
+ **options.merge(type_system: NxtSchema::Types::Params),
44
+ &block
45
+ )
46
+ end
47
+ end
38
48
  end
@@ -0,0 +1,4 @@
1
+ module NxtSchema
2
+ class Error < StandardError
3
+ end
4
+ end
@@ -1,7 +1,6 @@
1
1
  module NxtSchema
2
2
  module Errors
3
- class Error < StandardError
4
-
3
+ class CoercionError < Error
5
4
  end
6
5
  end
7
6
  end
@@ -0,0 +1,16 @@
1
+ module NxtSchema
2
+ module Errors
3
+ class Invalid < NxtSchema::Error
4
+ def initialize(node)
5
+ @node = node
6
+ super(build_message)
7
+ end
8
+
9
+ attr_reader :node
10
+
11
+ def build_message
12
+ node.errors
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,6 @@
1
+ module NxtSchema
2
+ module Errors
3
+ class InvalidOptions < NxtSchema::Error
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,39 @@
1
+ module NxtSchema
2
+ module Node
3
+ class AnyOf < Node::Base
4
+ def valid?
5
+ valid_node.present?
6
+ end
7
+
8
+ def call
9
+ child_nodes.map(&:call)
10
+
11
+ if valid?
12
+ self.output = valid_node.output
13
+ else
14
+ child_nodes.each do |node|
15
+ merge_errors(node)
16
+ end
17
+ end
18
+
19
+ self
20
+ end
21
+
22
+ private
23
+
24
+ delegate :[], to: :child_nodes
25
+
26
+ def valid_node
27
+ child_nodes.find(&:valid?)
28
+ end
29
+
30
+ def child_nodes
31
+ @child_nodes ||= nodes.map { |node| node.build_node(input: input, context: context, parent: self) }
32
+ end
33
+
34
+ def nodes
35
+ @nodes ||= node.sub_nodes.values
36
+ end
37
+ end
38
+ end
39
+ end
@@ -1,317 +1,116 @@
1
1
  module NxtSchema
2
2
  module Node
3
3
  class Base
4
- def initialize(name: name_from_index, type:, parent_node:, **options, &block)
5
- @name = name
6
- @parent_node = parent_node
7
- @options = options
8
- @type_system = resolve_type_system
9
- @additional_keys_strategy = resolve_additional_keys_strategy
10
- @type = type
11
- @schema_errors_key = options.fetch(:schema_errors_key, :itself)
12
- @validations = []
13
- @level = parent_node ? parent_node.level + 1 : 0
14
- @all_nodes = parent_node ? (parent_node.all_nodes || {}) : {}
15
- @is_root = parent_node.nil?
16
- @root = parent_node.nil? ? self : parent_node.root
17
- @errors = {}
18
- @context = nil
19
- @applied = false
20
- @input = nil
21
- @value = NxtSchema::Undefined.new
22
- @locale = options.fetch(:locale) { parent_node&.locale || 'en' }
4
+ def initialize(node:, input: Undefined.new, parent:, context:, error_key:)
5
+ @node = node
6
+ @input = input
7
+ @parent = parent
8
+ @output = nil
9
+ @error_key = error_key
10
+ @context = context || parent&.context
11
+ @coerced = false
12
+ @coerced_nodes = parent&.coerced_nodes || []
13
+ @is_root = parent.nil?
14
+ @root = parent.nil? ? self : parent.root
15
+ @errors = ErrorStore.new(self)
16
+ @locale = node.options.fetch(:locale) { parent&.locale || 'en' }.to_s
23
17
 
24
- # Note that it is not possible to use present? on an instance of NxtSchema::Schema since it inherits from Hash
25
- evaluate_block(block) if block_given?
18
+ @index = error_key
19
+ resolve_error_key(error_key)
26
20
  end
27
21
 
28
- attr_accessor :name,
29
- :parent_node,
30
- :options,
31
- :type,
32
- :schema_errors,
33
- :namespace,
34
- :errors,
35
- :validations,
36
- :schema_errors_key,
37
- :level,
38
- :validation_errors,
39
- :all_nodes,
40
- :value,
41
- :type_system,
42
- :root,
43
- :context,
44
- :applied,
45
- :input,
46
- :additional_keys_strategy,
47
- :locale
22
+ attr_accessor :output, :node, :input
23
+ attr_reader :parent, :context, :error_key, :coerced, :coerced_nodes, :root, :errors, :locale, :index
48
24
 
49
-
50
- alias_method :types, :type_system
51
-
52
- def parent(level = 1)
53
- level.times.inject(self) { |acc| acc.parent_node }
54
- end
55
-
56
- alias_method :up, :parent
57
-
58
- def default(default_value, &block)
59
- options.merge!(default: default_value)
60
- evaluate_block(block) if block_given?
61
- self
25
+ def call
26
+ raise NotImplementedError, 'Implement this in our sub class'
62
27
  end
63
28
 
64
- def meta(value = NxtSchema::Undefined.new)
65
- if value.is_a?(NxtSchema::Undefined)
66
- @meta
67
- else
68
- @meta = value
69
- self
70
- end
71
- end
72
-
73
- def value_or_default_value(value)
74
- if !value && options.key?(:default)
75
- DefaultValueEvaluator.new(self, options.fetch(:default)).call
76
- else
77
- value
78
- end
79
- end
29
+ delegate :name, :options, to: :node
80
30
 
81
- def maybe(maybe_value, &block)
82
- options.merge!(maybe: maybe_value)
83
- evaluate_block(block) if block_given?
84
- self
31
+ def root?
32
+ @is_root
85
33
  end
86
34
 
87
- def optional(optional_value, &block)
88
- raise ArgumentError, 'Optional nodes can only exist within schemas' unless parent.is_a?(NxtSchema::Node::Schema)
89
-
90
- options.merge!(optional: optional_value)
91
- evaluate_block(block) if block_given?
92
- self
35
+ def valid?
36
+ errors.empty?
93
37
  end
94
38
 
95
- def presence(presence_value, &block)
96
- raise ArgumentError, 'Present nodes can only exist within schemas' unless parent.is_a?(NxtSchema::Node::Schema)
97
-
98
- options.merge!(presence: presence_value)
99
- evaluate_block(block) if block_given?
100
- self
39
+ def add_error(error)
40
+ errors.add_validation_error(message: error)
101
41
  end
102
42
 
103
- def presence?
104
- @presence ||= begin
105
- presence_option = options[:presence]
106
-
107
- options[:presence] = if presence_option.respond_to?(:call)
108
- Callable.new(presence_option).call(self, value)
109
- else
110
- presence_option
111
- end
112
- end
43
+ def add_schema_error(error)
44
+ errors.add_schema_error(message: error)
113
45
  end
114
46
 
115
- def validate(key, *args, &block)
116
- if key.is_a?(Symbol)
117
- validator = validator(key, *args)
118
- elsif key.respond_to?(:call)
119
- validator = key
120
- else
121
- raise ArgumentError, "Don't know how to resolve validator from: #{key}"
122
- end
123
-
124
- add_validators(validator)
125
- evaluate_block(block) if block_given?
126
- self
47
+ def merge_errors(node)
48
+ errors.merge_errors(node)
127
49
  end
128
50
 
129
- def add_error(error, index = schema_errors_key)
130
- validation_errors[index] ||= []
131
- validation_errors[index] << error
132
- false
133
- end
51
+ def run_validations
52
+ return false unless coerced?
134
53
 
135
- def validate_all_nodes
136
- sorted_nodes = all_nodes.values.sort do |node, other_node|
137
- [node.level, (!node.leaf?).to_s] <=> [other_node.level, (!other_node.leaf?).to_s]
54
+ node.validations.each do |validation|
55
+ args = [self, input]
56
+ validation.call(*args.take(validation.arity))
138
57
  end
139
-
140
- # we have to start from the bottom, leafs before others on the same level
141
- sorted_nodes.reverse_each(&:apply_validations)
142
58
  end
143
59
 
144
- def apply_validations
145
- # We don't run validations in case there are schema errors
146
- # to avoid weird errors
147
- # First reject empty schema_errors
148
- schema_errors.reject! { |_, v| v.empty? }
149
-
150
- # TODO: Is this correct? - Do not apply validations when maybe criteria applies?
151
- unless schema_errors[schema_errors_key]&.any? && !maybe_criteria_applies?(value)
152
- build_validations
60
+ def up(levels = 1)
61
+ 0.upto(levels - 1).inject(self) do |acc, _|
62
+ parent = acc.send(:parent)
63
+ break acc unless parent
153
64
 
154
- validations.each do |validation|
155
- args = [self, value]
156
- validation.call(*args.take(validation.arity))
157
- end
65
+ parent
158
66
  end
159
-
160
- if self.is_a?(NxtSchema::Node::Collection) && value.respond_to?(:each)
161
- value.each_with_index do |item, index|
162
- validation_errors[index]&.reject! { |_, v| v.empty? }
163
- end
164
- end
165
-
166
- validation_errors.reject! { |_, v| v.empty? }
167
-
168
- self
169
- end
170
-
171
- def build_validations
172
- validations_from_options = Array(options.fetch(:validate, []))
173
- self.validations = validations_from_options
174
- end
175
-
176
- def schema_errors?
177
- schema_errors.reject! { |_, v| v.empty? }
178
- schema_errors.any?
179
- end
180
-
181
- def validation_errors?
182
- validation_errors.reject! { |_, v| v.empty? }
183
- validation_errors.any?
184
- end
185
-
186
- def root?
187
- @is_root
188
- end
189
-
190
- def leaf?
191
- false
192
- end
193
-
194
- def valid?
195
- raise SchemaNotAppliedError, 'Schema was not applied yet' unless applied?
196
-
197
- validation_errors.empty?
198
- end
199
-
200
- def add_validators(validator)
201
- options[:validate] ||= []
202
- options[:validate] = Array(options.fetch(:validate, []))
203
- options[:validate] << validator
204
- end
205
-
206
- def validator(key, *args)
207
- Validators::Registry::VALIDATORS.resolve(key).new(*args).build
208
- end
209
-
210
- def validate_with(&block)
211
- add_validators(
212
- ->(node) { NxtSchema::Node::ValidateWithProxy.new(node).validate(&block) }
213
- )
214
67
  end
215
68
 
216
69
  private
217
70
 
218
- def register_node(context)
219
- return if all_nodes.key?(object_id)
71
+ attr_writer :coerced, :root
220
72
 
221
- self.context = context
222
- all_nodes[object_id] = self
223
- end
73
+ def coerce_input
74
+ output = input.is_a?(Undefined) && node.omnipresent? ? input : node.type.call(input)
75
+ self.output = output
224
76
 
225
- def applied?
226
- @applied
77
+ rescue Dry::Types::CoercionError, NxtSchema::Errors::CoercionError => error
78
+ add_schema_error(error.message)
227
79
  end
228
80
 
229
- def mark_as_applied
230
- self.applied = true
81
+ def apply_on_evaluators
82
+ node.on_evaluators.each { |evaluator| evaluator.call(input, self, context) { |result| self.input = result } }
231
83
  end
232
84
 
233
- def add_schema_error(error, index = schema_errors_key)
234
- schema_errors[index] ||= []
235
- schema_errors[index] << error
85
+ def maybe_evaluator_applies?
86
+ @maybe_evaluator_applies ||= node.maybe_evaluators.inject(false) do |acc, evaluator|
87
+ result = (acc || evaluator.call(input, self, context))
236
88
 
237
- add_error(error, index)
238
- end
239
-
240
- def maybe_criteria_applies?(value)
241
- @maybe_criteria_applies ||= begin
242
- options.key?(:maybe) && MaybeEvaluator.new(self, options.fetch(:maybe), value).call
243
- end
244
- end
245
-
246
- def self_without_empty_schema_errors
247
- schema_errors.reject! { |_, v| v.empty? }
248
- validate_all_nodes if root?
249
- self.errors = flat_validation_errors(validation_errors, name)
250
- self
251
- end
252
-
253
- def flat_validation_errors(errors, namespace, acc = {})
254
- errors.each_with_object(acc) do |(key, val), acc|
255
- current_namespace = [namespace, key].reject { |namespace| namespace == schema_errors_key }.compact.join('.')
256
-
257
- if val.is_a?(::Hash)
258
- flat_validation_errors(val, current_namespace, acc)
259
- else
260
- acc[current_namespace] ||= []
261
- acc[current_namespace] += Array(val)
262
- end
263
- end
264
- end
265
-
266
- def name_from_index
267
- if parent_node
268
- if parent_node.is_a?(NxtSchema::Node::Collection)
269
- size + 1
89
+ if result
90
+ self.output = input
91
+ break true
270
92
  else
271
- raise ArgumentError, "Nodes with parent_node: #{parent_node} cannot be anonymous"
93
+ false
272
94
  end
273
- else
274
- :root
275
95
  end
276
96
  end
277
97
 
278
- def evaluate_block(block)
279
- if block.arity.zero?
280
- instance_exec(&block)
281
- else
282
- evaluator_args = [self, value]
283
- block.call(*evaluator_args.take(block.arity))
284
- end
285
- end
286
-
287
- def resolve_type_system
288
- type_system = options.fetch(:type_system) { parent_node&.type_system }
98
+ def register_as_coerced_when_no_errors
99
+ return unless valid?
289
100
 
290
- self.type_system = if type_system.is_a?(Module)
291
- type_system
292
- elsif type_system.is_a?(Symbol) || type_system.is_a?(String)
293
- "NxtSchema::Types::#{type_system.to_s.classify}".constantize
294
- else
295
- NxtSchema::Types
296
- end
101
+ self.coerced = true
102
+ coerced_nodes << self
297
103
  end
298
104
 
299
- def resolve_additional_keys_strategy
300
- options.fetch(:additional_keys) { parent_node&.send(:resolve_additional_keys_strategy) || :ignore }
301
- end
302
-
303
- def type_resolver
304
- @type_resolver ||= begin
305
- if root?
306
- TypeResolver.new
307
- else
308
- raise NoMethodError, 'type_resolver is only available on root node'
309
- end
310
- end
105
+ def resolve_error_key(key)
106
+ parts = [parent&.error_key].compact
107
+ parts << (key.present? ? "#{node.name}[#{key}]" : node.name)
108
+ @error_key = parts.join('.')
311
109
  end
312
110
 
313
- def coerce_value(value)
314
- type[value]
111
+ def coerced?(&block)
112
+ block.call(self) if @coerced && block_given?
113
+ @coerced
315
114
  end
316
115
  end
317
116
  end