lite-validation 0.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.
- checksums.yaml +7 -0
- data/.rubocop.yml +94 -0
- data/Gemfile +18 -0
- data/README.md +770 -0
- data/bench/calibrational.rb +89 -0
- data/bench/comparative.rb +197 -0
- data/bench/functional.rb +107 -0
- data/bench/profile.rb +45 -0
- data/lib/lite/validation/adapters/interfaces/dry.rb +29 -0
- data/lib/lite/validation/error.rb +9 -0
- data/lib/lite/validation/result/abstract/disputable.rb +17 -0
- data/lib/lite/validation/result/abstract/failure.rb +19 -0
- data/lib/lite/validation/result/abstract/refutable.rb +21 -0
- data/lib/lite/validation/result/abstract/success.rb +25 -0
- data/lib/lite/validation/result/abstract.rb +16 -0
- data/lib/lite/validation/structured_error/record.rb +36 -0
- data/lib/lite/validation/structured_error.rb +23 -0
- data/lib/lite/validation/validator/adapters/errors/default.rb +24 -0
- data/lib/lite/validation/validator/adapters/interfaces/default.rb +32 -0
- data/lib/lite/validation/validator/adapters/interfaces/dry.rb +17 -0
- data/lib/lite/validation/validator/adapters/predicates/dry/adapter.rb +50 -0
- data/lib/lite/validation/validator/adapters/predicates/dry/builder.rb +46 -0
- data/lib/lite/validation/validator/adapters/predicates/dry/engine.rb +32 -0
- data/lib/lite/validation/validator/adapters/predicates/dry.rb +3 -0
- data/lib/lite/validation/validator/coordinator/builder.rb +92 -0
- data/lib/lite/validation/validator/coordinator/default.rb +32 -0
- data/lib/lite/validation/validator/coordinator/errors/builder.rb +56 -0
- data/lib/lite/validation/validator/coordinator/errors/dry.rb +29 -0
- data/lib/lite/validation/validator/coordinator/errors/flat.rb +46 -0
- data/lib/lite/validation/validator/coordinator/errors/hierarchical.rb +29 -0
- data/lib/lite/validation/validator/coordinator/instance.rb +30 -0
- data/lib/lite/validation/validator/coordinator.rb +12 -0
- data/lib/lite/validation/validator/helpers/path.rb +49 -0
- data/lib/lite/validation/validator/node/abstract/branch.rb +21 -0
- data/lib/lite/validation/validator/node/abstract/instance.rb +43 -0
- data/lib/lite/validation/validator/node/abstract/leaf.rb +35 -0
- data/lib/lite/validation/validator/node/abstract.rb +25 -0
- data/lib/lite/validation/validator/node/child.rb +44 -0
- data/lib/lite/validation/validator/node/implementation/apply_ruling.rb +44 -0
- data/lib/lite/validation/validator/node/implementation/dig.rb +38 -0
- data/lib/lite/validation/validator/node/implementation/helpers/call_foreign.rb +31 -0
- data/lib/lite/validation/validator/node/implementation/helpers/with_result.rb +23 -0
- data/lib/lite/validation/validator/node/implementation/helpers/yield_strategy.rb +83 -0
- data/lib/lite/validation/validator/node/implementation/helpers/yield_validator.rb +49 -0
- data/lib/lite/validation/validator/node/implementation/identity.rb +90 -0
- data/lib/lite/validation/validator/node/implementation/iteration/iterator.rb +102 -0
- data/lib/lite/validation/validator/node/implementation/iteration.rb +46 -0
- data/lib/lite/validation/validator/node/implementation/navigation.rb +43 -0
- data/lib/lite/validation/validator/node/implementation/predication.rb +61 -0
- data/lib/lite/validation/validator/node/implementation/scoping/evaluator.rb +43 -0
- data/lib/lite/validation/validator/node/implementation/scoping.rb +43 -0
- data/lib/lite/validation/validator/node/implementation/validation.rb +64 -0
- data/lib/lite/validation/validator/node/implementation/wrap.rb +26 -0
- data/lib/lite/validation/validator/node/root.rb +60 -0
- data/lib/lite/validation/validator/node/suspended.rb +33 -0
- data/lib/lite/validation/validator/node.rb +12 -0
- data/lib/lite/validation/validator/option/none.rb +43 -0
- data/lib/lite/validation/validator/option/some/abstract.rb +29 -0
- data/lib/lite/validation/validator/option/some/complex/registry/abstract.rb +67 -0
- data/lib/lite/validation/validator/option/some/complex/registry/node.rb +47 -0
- data/lib/lite/validation/validator/option/some/complex/registry/root.rb +31 -0
- data/lib/lite/validation/validator/option/some/complex/registry.rb +32 -0
- data/lib/lite/validation/validator/option/some/complex/wrappers/abstract/iterable.rb +31 -0
- data/lib/lite/validation/validator/option/some/complex/wrappers/abstract/non_iterable.rb +27 -0
- data/lib/lite/validation/validator/option/some/complex/wrappers/abstract.rb +35 -0
- data/lib/lite/validation/validator/option/some/complex/wrappers/array.rb +41 -0
- data/lib/lite/validation/validator/option/some/complex/wrappers/hash.rb +40 -0
- data/lib/lite/validation/validator/option/some/complex/wrappers/object.rb +34 -0
- data/lib/lite/validation/validator/option/some/complex/wrappers/tuple.rb +47 -0
- data/lib/lite/validation/validator/option/some/complex/wrappers.rb +5 -0
- data/lib/lite/validation/validator/option/some/complex.rb +24 -0
- data/lib/lite/validation/validator/option/some/dig.rb +34 -0
- data/lib/lite/validation/validator/option/some/simple.rb +23 -0
- data/lib/lite/validation/validator/option/some/singular.rb +29 -0
- data/lib/lite/validation/validator/option/some.rb +20 -0
- data/lib/lite/validation/validator/option.rb +20 -0
- data/lib/lite/validation/validator/predicate/abstract/variants.rb +23 -0
- data/lib/lite/validation/validator/predicate/foreign/adapter/input/single.rb +21 -0
- data/lib/lite/validation/validator/predicate/foreign/adapter/input/tuple.rb +21 -0
- data/lib/lite/validation/validator/predicate/foreign/adapter/input.rb +28 -0
- data/lib/lite/validation/validator/predicate/foreign/adapter/ruling/instance.rb +37 -0
- data/lib/lite/validation/validator/predicate/foreign/adapter/ruling.rb +26 -0
- data/lib/lite/validation/validator/predicate/foreign/engine.rb +27 -0
- data/lib/lite/validation/validator/predicate/foreign/variant.rb +33 -0
- data/lib/lite/validation/validator/predicate/foreign/variants.rb +46 -0
- data/lib/lite/validation/validator/predicate/native/builder.rb +72 -0
- data/lib/lite/validation/validator/predicate/native/definite.rb +19 -0
- data/lib/lite/validation/validator/predicate/native/instance.rb +41 -0
- data/lib/lite/validation/validator/predicate/native/optional.rb +34 -0
- data/lib/lite/validation/validator/predicate/registry.rb +47 -0
- data/lib/lite/validation/validator/predicate.rb +17 -0
- data/lib/lite/validation/validator/result/abstract/failure.rb +21 -0
- data/lib/lite/validation/validator/result/abstract/instance.rb +18 -0
- data/lib/lite/validation/validator/result/abstract/success.rb +17 -0
- data/lib/lite/validation/validator/result/abstract.rb +29 -0
- data/lib/lite/validation/validator/result/committed.rb +75 -0
- data/lib/lite/validation/validator/result/disputable/hash.rb +17 -0
- data/lib/lite/validation/validator/result/disputable/instance.rb +43 -0
- data/lib/lite/validation/validator/result/disputable/iterable/array.rb +23 -0
- data/lib/lite/validation/validator/result/disputable/iterable.rb +17 -0
- data/lib/lite/validation/validator/result/disputable/navigable.rb +35 -0
- data/lib/lite/validation/validator/result/disputable.rb +14 -0
- data/lib/lite/validation/validator/result/disputed/abstract/hash.rb +32 -0
- data/lib/lite/validation/validator/result/disputed/abstract/instance.rb +26 -0
- data/lib/lite/validation/validator/result/disputed/iterable/array.rb +42 -0
- data/lib/lite/validation/validator/result/disputed/iterable/hash.rb +38 -0
- data/lib/lite/validation/validator/result/disputed/iterable.rb +20 -0
- data/lib/lite/validation/validator/result/disputed/navigable.rb +59 -0
- data/lib/lite/validation/validator/result/disputed.rb +17 -0
- data/lib/lite/validation/validator/result/refuted.rb +78 -0
- data/lib/lite/validation/validator/result/valid/abstract/collect.rb +42 -0
- data/lib/lite/validation/validator/result/valid/abstract/commit.rb +25 -0
- data/lib/lite/validation/validator/result/valid/abstract/instance.rb +23 -0
- data/lib/lite/validation/validator/result/valid/iterable/array/abstract.rb +24 -0
- data/lib/lite/validation/validator/result/valid/iterable/array/tuples.rb +64 -0
- data/lib/lite/validation/validator/result/valid/iterable/array/values.rb +42 -0
- data/lib/lite/validation/validator/result/valid/iterable/hash.rb +46 -0
- data/lib/lite/validation/validator/result/valid/iterable.rb +33 -0
- data/lib/lite/validation/validator/result/valid/navigable.rb +68 -0
- data/lib/lite/validation/validator/result/valid.rb +21 -0
- data/lib/lite/validation/validator/result.rb +15 -0
- data/lib/lite/validation/validator/ruling/abstract/invalid.rb +59 -0
- data/lib/lite/validation/validator/ruling/abstract/valid.rb +23 -0
- data/lib/lite/validation/validator/ruling/abstract.rb +12 -0
- data/lib/lite/validation/validator/ruling/commit.rb +17 -0
- data/lib/lite/validation/validator/ruling/dispute.rb +21 -0
- data/lib/lite/validation/validator/ruling/invalidate.rb +32 -0
- data/lib/lite/validation/validator/ruling/pass.rb +19 -0
- data/lib/lite/validation/validator/ruling/refute.rb +21 -0
- data/lib/lite/validation/validator/ruling.rb +53 -0
- data/lib/lite/validation/validator/state/instance.rb +46 -0
- data/lib/lite/validation/validator/state/merge_strategy.rb +50 -0
- data/lib/lite/validation/validator/state/unwrap_strategy.rb +31 -0
- data/lib/lite/validation/validator/state.rb +21 -0
- data/lib/lite/validation/validator.rb +15 -0
- data/lib/lite/validation/version.rb +9 -0
- data/lib/lite/validation.rb +8 -0
- metadata +196 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../result'
|
|
4
|
+
require_relative '../../option'
|
|
5
|
+
|
|
6
|
+
module Lite
|
|
7
|
+
module Validation
|
|
8
|
+
module Validator
|
|
9
|
+
module Adapters
|
|
10
|
+
module Interfaces
|
|
11
|
+
module Default
|
|
12
|
+
def self.success(value)
|
|
13
|
+
Result::Committed.instance(value)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.failure(error)
|
|
17
|
+
Result::Refuted.instance(error)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.some(value)
|
|
21
|
+
Option.some(value)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.none
|
|
25
|
+
Option.none
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../../adapters/interfaces/dry'
|
|
4
|
+
|
|
5
|
+
module Lite
|
|
6
|
+
module Validation
|
|
7
|
+
module Validator
|
|
8
|
+
module Adapters
|
|
9
|
+
module Interfaces
|
|
10
|
+
module Dry
|
|
11
|
+
extend Validation::Adapters::Interfaces::Dry
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'forwardable'
|
|
4
|
+
require 'dry-logic'
|
|
5
|
+
|
|
6
|
+
require_relative '../../../predicate/foreign/adapter/input'
|
|
7
|
+
require_relative '../../../predicate/foreign/adapter/ruling'
|
|
8
|
+
|
|
9
|
+
module Lite
|
|
10
|
+
module Validation
|
|
11
|
+
module Validator
|
|
12
|
+
module Adapters
|
|
13
|
+
module Predicates
|
|
14
|
+
module Dry
|
|
15
|
+
class Adapter
|
|
16
|
+
extend Forwardable
|
|
17
|
+
|
|
18
|
+
Lite::Data.define(self, args: %i[error_adapter input_adapter ruling_adapter])
|
|
19
|
+
|
|
20
|
+
def self.instance(error_adapter, arity, severity)
|
|
21
|
+
new(
|
|
22
|
+
error_adapter,
|
|
23
|
+
Predicate::Foreign::Adapter::Input.instance(arity),
|
|
24
|
+
Predicate::Foreign::Adapter::Ruling.instance(severity)
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def_delegator :input_adapter, :pass_in
|
|
29
|
+
def_delegator :ruling_adapter, :severity
|
|
30
|
+
|
|
31
|
+
def to_ruling(result, rule, value)
|
|
32
|
+
return Validator::Ruling::Pass() if result.success?
|
|
33
|
+
|
|
34
|
+
ruling_adapter.to_ruling(error_adapter.call(rule, value))
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def dispute
|
|
38
|
+
severity == :dispute ? self : with(ruling_adapter: ruling_adapter.dispute)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def refute
|
|
42
|
+
severity == :refute ? self : with(ruling_adapter: ruling_adapter.refute)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'dry-logic'
|
|
4
|
+
require 'forwardable'
|
|
5
|
+
|
|
6
|
+
require_relative '../../../predicate/foreign/engine'
|
|
7
|
+
require_relative 'adapter'
|
|
8
|
+
|
|
9
|
+
module Lite
|
|
10
|
+
module Validation
|
|
11
|
+
module Validator
|
|
12
|
+
module Adapters
|
|
13
|
+
module Predicates
|
|
14
|
+
module Dry
|
|
15
|
+
class Builder
|
|
16
|
+
def initialize(error_adapter, arity, severity: :dispute)
|
|
17
|
+
@error_adapter = error_adapter
|
|
18
|
+
@arity = arity
|
|
19
|
+
@severity = severity
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def call(&block)
|
|
23
|
+
rule = ::Dry::Logic::Builder.call(&block)
|
|
24
|
+
|
|
25
|
+
Predicate::Foreign::Variant.new(rule, adapter_instance)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def severity(severity)
|
|
29
|
+
@severity = severity
|
|
30
|
+
self
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
attr_reader :error_adapter, :arity
|
|
36
|
+
|
|
37
|
+
def adapter_instance
|
|
38
|
+
Adapter.instance(error_adapter, arity, @severity)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'builder'
|
|
4
|
+
require_relative '../../../predicate/registry'
|
|
5
|
+
|
|
6
|
+
module Lite
|
|
7
|
+
module Validation
|
|
8
|
+
module Validator
|
|
9
|
+
module Adapters
|
|
10
|
+
module Predicates
|
|
11
|
+
module Dry
|
|
12
|
+
class Engine < Predicate::Foreign::Engine
|
|
13
|
+
def self.instance(error_adapter)
|
|
14
|
+
new error_adapter
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def build_contextual(keys, context, &block)
|
|
18
|
+
definite = block.call(Builder.new(error_adapter, keys.length), context)
|
|
19
|
+
Predicate::Foreign::Variants.new(definite: definite)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def build(keys, severity: :dispute, &block)
|
|
23
|
+
definite = Builder.new(error_adapter, keys.length, severity: severity).call(&block)
|
|
24
|
+
Predicate::Foreign::Variants.new(definite: definite)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'errors/builder'
|
|
4
|
+
|
|
5
|
+
require_relative 'default'
|
|
6
|
+
require_relative '../node/root'
|
|
7
|
+
|
|
8
|
+
module Lite
|
|
9
|
+
module Validation
|
|
10
|
+
module Validator
|
|
11
|
+
module Coordinator
|
|
12
|
+
class Builder
|
|
13
|
+
include Ruling::Constructors
|
|
14
|
+
|
|
15
|
+
def self.define(&block)
|
|
16
|
+
new.tap { _1.instance_eval(&block) }.build
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def initialize
|
|
20
|
+
@validation_error_adapter = nil
|
|
21
|
+
@final_error_adapter = nil
|
|
22
|
+
@interface_adapter = nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def validation_error_adapter(&block)
|
|
26
|
+
if block
|
|
27
|
+
raise(Error, 'Validation error adapter already set') unless @validation_error_adapter.nil?
|
|
28
|
+
|
|
29
|
+
@validation_error_adapter = Errors::Builder.define(&block)
|
|
30
|
+
|
|
31
|
+
else
|
|
32
|
+
@validation_error_adapter
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def final_error_adapter(*args)
|
|
37
|
+
case args.length
|
|
38
|
+
when 1
|
|
39
|
+
raise(Error, 'Final error adapter already set') unless @final_error_adapter.nil?
|
|
40
|
+
|
|
41
|
+
@final_error_adapter = args[0]
|
|
42
|
+
when 0
|
|
43
|
+
@final_error_adapter
|
|
44
|
+
else raise Error, "Unexpected arguments to builder: #{args.inspect}"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def interface_adapter(*args)
|
|
49
|
+
case args.length
|
|
50
|
+
when 1
|
|
51
|
+
raise(Error, 'Monads adapter already set') unless @interface_adapter.nil?
|
|
52
|
+
|
|
53
|
+
@interface_adapter = args[0]
|
|
54
|
+
|
|
55
|
+
when 0 then @interface_adapter
|
|
56
|
+
else raise Error, "Unexpected arguments to builder: #{args.inspect}"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def build
|
|
61
|
+
result = validate
|
|
62
|
+
raise Error, "Builder invalid: #{result.error.message}" unless result.success?
|
|
63
|
+
|
|
64
|
+
Coordinator::Instance.new(
|
|
65
|
+
interface: interface_adapter,
|
|
66
|
+
validation_error: validation_error_adapter.build,
|
|
67
|
+
final_error: final_error_adapter
|
|
68
|
+
)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def validate # rubocop:disable Metrics/AbcSize
|
|
72
|
+
coordinator = Coordinator::Default.instance(Errors::Flat)
|
|
73
|
+
|
|
74
|
+
Validator::Node::Root.initial(self, coordinator).at do |builder|
|
|
75
|
+
builder.validate(:interface_adapter) do |adapter|
|
|
76
|
+
Refute(coordinator.internal_error(:value_missing)) if adapter.nil?
|
|
77
|
+
end.validate(:final_error_adapter) do |adapter|
|
|
78
|
+
Refute(coordinator.internal_error(:value_missing)) if adapter.nil?
|
|
79
|
+
end.at(:validation_error_adapter) do |errors|
|
|
80
|
+
errors.validate do |adapter|
|
|
81
|
+
Refute(coordinator.internal_error(:value_missing)) if adapter.nil?
|
|
82
|
+
end.validate(:structured_error) do |proc|
|
|
83
|
+
Refute(coordinator.internal_error(:value_missing)) if proc.nil?
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end.to_result
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../adapters/errors/default'
|
|
4
|
+
require_relative '../adapters/interfaces/default'
|
|
5
|
+
require_relative 'instance'
|
|
6
|
+
|
|
7
|
+
module Lite
|
|
8
|
+
module Validation
|
|
9
|
+
module Validator
|
|
10
|
+
module Coordinator
|
|
11
|
+
class Default < Instance
|
|
12
|
+
def self.instance(error_building_strategy)
|
|
13
|
+
monads = Adapters::Interfaces::Default
|
|
14
|
+
errors = Adapters::Errors::Default
|
|
15
|
+
new interface: monads, validation_error: errors, final_error: error_building_strategy
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def_delegator :validation_error, :structured_error
|
|
19
|
+
|
|
20
|
+
def build_final_error(result)
|
|
21
|
+
errors = final_error.build(result)
|
|
22
|
+
message = errors.flat_map do |(key, errors)|
|
|
23
|
+
key.empty? ? errors.join(', ') : "#{key}: #{errors.map(&:code).join(', ')}"
|
|
24
|
+
end.join(', ')
|
|
25
|
+
|
|
26
|
+
structured_error(:invalid, message: message, data: errors)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../../error'
|
|
4
|
+
require_relative 'dry'
|
|
5
|
+
require_relative 'flat'
|
|
6
|
+
require_relative 'hierarchical'
|
|
7
|
+
|
|
8
|
+
module Lite
|
|
9
|
+
module Validation
|
|
10
|
+
module Validator
|
|
11
|
+
module Coordinator
|
|
12
|
+
module Errors
|
|
13
|
+
class Builder
|
|
14
|
+
def self.define(&block)
|
|
15
|
+
new.tap { _1.instance_eval(&block) }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def initialize
|
|
19
|
+
@internal_error = nil
|
|
20
|
+
@structured_error = nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def build
|
|
24
|
+
builder = self
|
|
25
|
+
|
|
26
|
+
Module.new do
|
|
27
|
+
define_singleton_method :internal_error, &builder.internal_error || builder.structured_error
|
|
28
|
+
define_singleton_method :structured_error, &builder.structured_error
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def internal_error(&block)
|
|
33
|
+
if block
|
|
34
|
+
raise(Error, 'Internal error proc already set') unless @internal_error.nil?
|
|
35
|
+
|
|
36
|
+
@internal_error = block
|
|
37
|
+
else
|
|
38
|
+
@internal_error
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def structured_error(&block)
|
|
43
|
+
if block
|
|
44
|
+
raise(Error, 'Structured error proc already set') unless @structured_error.nil?
|
|
45
|
+
|
|
46
|
+
@structured_error = block
|
|
47
|
+
else
|
|
48
|
+
@structured_error
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lite
|
|
4
|
+
module Validation
|
|
5
|
+
module Validator
|
|
6
|
+
module Coordinator
|
|
7
|
+
module Errors
|
|
8
|
+
module Dry
|
|
9
|
+
def self.build(result)
|
|
10
|
+
root_errors = result.send(:errors_root)
|
|
11
|
+
children = result.send(:children).each_with_object({}) do |(key, child), acc|
|
|
12
|
+
child_errors = build(child)
|
|
13
|
+
acc[key] = child_errors unless child_errors.empty?
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
if children.empty?
|
|
17
|
+
root_errors
|
|
18
|
+
elsif root_errors.empty?
|
|
19
|
+
children
|
|
20
|
+
else
|
|
21
|
+
[root_errors, children]
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lite
|
|
4
|
+
module Validation
|
|
5
|
+
module Validator
|
|
6
|
+
module Coordinator
|
|
7
|
+
module Errors
|
|
8
|
+
module Flat
|
|
9
|
+
def self.build(result)
|
|
10
|
+
build_recursively(result)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.build_recursively(result, path: [])
|
|
14
|
+
errors_root = result.send(:errors_root)
|
|
15
|
+
return build_nested(result, path) if errors_root.empty?
|
|
16
|
+
|
|
17
|
+
[[stringify_path(path), errors_root], *build_nested(result, path)]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.build_nested(result, path)
|
|
21
|
+
result.send(:children).flat_map do |key, child|
|
|
22
|
+
build_recursively(child, path: [*path, key])
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.stringify_path(path)
|
|
27
|
+
case path
|
|
28
|
+
when Array then path.map { stringify_key(_1) }.join('.')
|
|
29
|
+
else path.to_s.freeze
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.stringify_key(key)
|
|
34
|
+
case key
|
|
35
|
+
when Array then "(#{key.map { stringify_path(_1) }.join(',')})"
|
|
36
|
+
else key.to_s.freeze
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private_class_method :build_recursively, :build_nested
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lite
|
|
4
|
+
module Validation
|
|
5
|
+
module Validator
|
|
6
|
+
module Coordinator
|
|
7
|
+
module Errors
|
|
8
|
+
module Hierarchical
|
|
9
|
+
def self.build(result)
|
|
10
|
+
root_errors = result.send(:errors_root)
|
|
11
|
+
children = result.send(:children).each_with_object({}) do |(key, child), acc|
|
|
12
|
+
child_errors = build(child)
|
|
13
|
+
acc[key] = child_errors unless child_errors.empty?
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
if children.empty?
|
|
17
|
+
{ errors: root_errors }
|
|
18
|
+
elsif root_errors.empty?
|
|
19
|
+
{ children: children }
|
|
20
|
+
else
|
|
21
|
+
{ errors: root_errors, children: children }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'lite/data'
|
|
4
|
+
require 'forwardable'
|
|
5
|
+
|
|
6
|
+
module Lite
|
|
7
|
+
module Validation
|
|
8
|
+
module Validator
|
|
9
|
+
module Coordinator
|
|
10
|
+
class Instance
|
|
11
|
+
extend Forwardable
|
|
12
|
+
|
|
13
|
+
Lite::Data.define(self, kwargs: %i[interface validation_error final_error])
|
|
14
|
+
|
|
15
|
+
def_delegator :interface, :failure
|
|
16
|
+
def_delegator :interface, :success
|
|
17
|
+
def_delegator :interface, :none
|
|
18
|
+
def_delegator :interface, :some
|
|
19
|
+
def_delegator :interface, :handle_result
|
|
20
|
+
def_delegator :validation_error, :internal_error
|
|
21
|
+
def_delegator :validation_error, :structured_error
|
|
22
|
+
|
|
23
|
+
def build_final_error(result)
|
|
24
|
+
final_error.build(result)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../error'
|
|
4
|
+
|
|
5
|
+
module Lite
|
|
6
|
+
module Validation
|
|
7
|
+
module Validator
|
|
8
|
+
module Helpers
|
|
9
|
+
module Path
|
|
10
|
+
class InvalidPath < Error::Fatal; end
|
|
11
|
+
|
|
12
|
+
def self.expand_path(path, base_path)
|
|
13
|
+
return [base_path] if path.empty?
|
|
14
|
+
|
|
15
|
+
element, *rest = path
|
|
16
|
+
|
|
17
|
+
case element
|
|
18
|
+
when ::Array
|
|
19
|
+
raise InvalidPath, <<~ERR.chomp unless rest.empty?
|
|
20
|
+
Can't follow path into a tuple: #{element} -> #{rest.map(&:inspect).join(', ')}
|
|
21
|
+
ERR
|
|
22
|
+
|
|
23
|
+
expand_tuple(element, base_path)
|
|
24
|
+
else
|
|
25
|
+
expand_path(rest, [*base_path, element])
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.expand_tuple(tuple, base_path)
|
|
30
|
+
tuple.flat_map do |element|
|
|
31
|
+
expand_element(element, base_path)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.expand_element(element, base_path)
|
|
36
|
+
case element
|
|
37
|
+
when ::Array
|
|
38
|
+
raise InvalidPath, "Empty path in a tuple: #{base_path.join(', ')}" if element.empty?
|
|
39
|
+
|
|
40
|
+
expand_path(element, base_path)
|
|
41
|
+
else
|
|
42
|
+
[[*base_path, element]]
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../abstract'
|
|
4
|
+
|
|
5
|
+
module Lite
|
|
6
|
+
module Validation
|
|
7
|
+
module Validator
|
|
8
|
+
module Node
|
|
9
|
+
module Abstract
|
|
10
|
+
module Branch
|
|
11
|
+
include Abstract
|
|
12
|
+
|
|
13
|
+
def inspect
|
|
14
|
+
"Branch #{super}"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'lite/data'
|
|
4
|
+
|
|
5
|
+
module Lite
|
|
6
|
+
module Validation
|
|
7
|
+
module Validator
|
|
8
|
+
module Node
|
|
9
|
+
module Abstract
|
|
10
|
+
class Instance
|
|
11
|
+
Lite::Data.define(self, args: %i[parent path option result state])
|
|
12
|
+
|
|
13
|
+
def self.instance(parent, path, option, result, state)
|
|
14
|
+
new(parent, path.freeze, option, result, state)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def coordinator
|
|
18
|
+
state.coordinator
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def value
|
|
22
|
+
state.unwrap_strategy.unwrap(option, coordinator)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def merge_strategy
|
|
26
|
+
state.merge_strategy
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def context
|
|
30
|
+
state.context
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def inspect
|
|
34
|
+
"#{display_path(true)} result=#{result.inspect} option=#{option.inspect} state=#{state.inspect}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private :option, :state, :with
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../abstract'
|
|
4
|
+
|
|
5
|
+
module Lite
|
|
6
|
+
module Validation
|
|
7
|
+
module Validator
|
|
8
|
+
module Node
|
|
9
|
+
module Abstract
|
|
10
|
+
module Leaf
|
|
11
|
+
include Abstract
|
|
12
|
+
|
|
13
|
+
def branch
|
|
14
|
+
self.class::Branch.instance parent, path, option.to_complex, result, state
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def inspect
|
|
18
|
+
"Leaf #{super}"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def dig(*path, from: nil, &block)
|
|
24
|
+
if path.empty?
|
|
25
|
+
super
|
|
26
|
+
else
|
|
27
|
+
branch.dig(*path, from: from, &block)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|