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.
Files changed (138) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +94 -0
  3. data/Gemfile +18 -0
  4. data/README.md +770 -0
  5. data/bench/calibrational.rb +89 -0
  6. data/bench/comparative.rb +197 -0
  7. data/bench/functional.rb +107 -0
  8. data/bench/profile.rb +45 -0
  9. data/lib/lite/validation/adapters/interfaces/dry.rb +29 -0
  10. data/lib/lite/validation/error.rb +9 -0
  11. data/lib/lite/validation/result/abstract/disputable.rb +17 -0
  12. data/lib/lite/validation/result/abstract/failure.rb +19 -0
  13. data/lib/lite/validation/result/abstract/refutable.rb +21 -0
  14. data/lib/lite/validation/result/abstract/success.rb +25 -0
  15. data/lib/lite/validation/result/abstract.rb +16 -0
  16. data/lib/lite/validation/structured_error/record.rb +36 -0
  17. data/lib/lite/validation/structured_error.rb +23 -0
  18. data/lib/lite/validation/validator/adapters/errors/default.rb +24 -0
  19. data/lib/lite/validation/validator/adapters/interfaces/default.rb +32 -0
  20. data/lib/lite/validation/validator/adapters/interfaces/dry.rb +17 -0
  21. data/lib/lite/validation/validator/adapters/predicates/dry/adapter.rb +50 -0
  22. data/lib/lite/validation/validator/adapters/predicates/dry/builder.rb +46 -0
  23. data/lib/lite/validation/validator/adapters/predicates/dry/engine.rb +32 -0
  24. data/lib/lite/validation/validator/adapters/predicates/dry.rb +3 -0
  25. data/lib/lite/validation/validator/coordinator/builder.rb +92 -0
  26. data/lib/lite/validation/validator/coordinator/default.rb +32 -0
  27. data/lib/lite/validation/validator/coordinator/errors/builder.rb +56 -0
  28. data/lib/lite/validation/validator/coordinator/errors/dry.rb +29 -0
  29. data/lib/lite/validation/validator/coordinator/errors/flat.rb +46 -0
  30. data/lib/lite/validation/validator/coordinator/errors/hierarchical.rb +29 -0
  31. data/lib/lite/validation/validator/coordinator/instance.rb +30 -0
  32. data/lib/lite/validation/validator/coordinator.rb +12 -0
  33. data/lib/lite/validation/validator/helpers/path.rb +49 -0
  34. data/lib/lite/validation/validator/node/abstract/branch.rb +21 -0
  35. data/lib/lite/validation/validator/node/abstract/instance.rb +43 -0
  36. data/lib/lite/validation/validator/node/abstract/leaf.rb +35 -0
  37. data/lib/lite/validation/validator/node/abstract.rb +25 -0
  38. data/lib/lite/validation/validator/node/child.rb +44 -0
  39. data/lib/lite/validation/validator/node/implementation/apply_ruling.rb +44 -0
  40. data/lib/lite/validation/validator/node/implementation/dig.rb +38 -0
  41. data/lib/lite/validation/validator/node/implementation/helpers/call_foreign.rb +31 -0
  42. data/lib/lite/validation/validator/node/implementation/helpers/with_result.rb +23 -0
  43. data/lib/lite/validation/validator/node/implementation/helpers/yield_strategy.rb +83 -0
  44. data/lib/lite/validation/validator/node/implementation/helpers/yield_validator.rb +49 -0
  45. data/lib/lite/validation/validator/node/implementation/identity.rb +90 -0
  46. data/lib/lite/validation/validator/node/implementation/iteration/iterator.rb +102 -0
  47. data/lib/lite/validation/validator/node/implementation/iteration.rb +46 -0
  48. data/lib/lite/validation/validator/node/implementation/navigation.rb +43 -0
  49. data/lib/lite/validation/validator/node/implementation/predication.rb +61 -0
  50. data/lib/lite/validation/validator/node/implementation/scoping/evaluator.rb +43 -0
  51. data/lib/lite/validation/validator/node/implementation/scoping.rb +43 -0
  52. data/lib/lite/validation/validator/node/implementation/validation.rb +64 -0
  53. data/lib/lite/validation/validator/node/implementation/wrap.rb +26 -0
  54. data/lib/lite/validation/validator/node/root.rb +60 -0
  55. data/lib/lite/validation/validator/node/suspended.rb +33 -0
  56. data/lib/lite/validation/validator/node.rb +12 -0
  57. data/lib/lite/validation/validator/option/none.rb +43 -0
  58. data/lib/lite/validation/validator/option/some/abstract.rb +29 -0
  59. data/lib/lite/validation/validator/option/some/complex/registry/abstract.rb +67 -0
  60. data/lib/lite/validation/validator/option/some/complex/registry/node.rb +47 -0
  61. data/lib/lite/validation/validator/option/some/complex/registry/root.rb +31 -0
  62. data/lib/lite/validation/validator/option/some/complex/registry.rb +32 -0
  63. data/lib/lite/validation/validator/option/some/complex/wrappers/abstract/iterable.rb +31 -0
  64. data/lib/lite/validation/validator/option/some/complex/wrappers/abstract/non_iterable.rb +27 -0
  65. data/lib/lite/validation/validator/option/some/complex/wrappers/abstract.rb +35 -0
  66. data/lib/lite/validation/validator/option/some/complex/wrappers/array.rb +41 -0
  67. data/lib/lite/validation/validator/option/some/complex/wrappers/hash.rb +40 -0
  68. data/lib/lite/validation/validator/option/some/complex/wrappers/object.rb +34 -0
  69. data/lib/lite/validation/validator/option/some/complex/wrappers/tuple.rb +47 -0
  70. data/lib/lite/validation/validator/option/some/complex/wrappers.rb +5 -0
  71. data/lib/lite/validation/validator/option/some/complex.rb +24 -0
  72. data/lib/lite/validation/validator/option/some/dig.rb +34 -0
  73. data/lib/lite/validation/validator/option/some/simple.rb +23 -0
  74. data/lib/lite/validation/validator/option/some/singular.rb +29 -0
  75. data/lib/lite/validation/validator/option/some.rb +20 -0
  76. data/lib/lite/validation/validator/option.rb +20 -0
  77. data/lib/lite/validation/validator/predicate/abstract/variants.rb +23 -0
  78. data/lib/lite/validation/validator/predicate/foreign/adapter/input/single.rb +21 -0
  79. data/lib/lite/validation/validator/predicate/foreign/adapter/input/tuple.rb +21 -0
  80. data/lib/lite/validation/validator/predicate/foreign/adapter/input.rb +28 -0
  81. data/lib/lite/validation/validator/predicate/foreign/adapter/ruling/instance.rb +37 -0
  82. data/lib/lite/validation/validator/predicate/foreign/adapter/ruling.rb +26 -0
  83. data/lib/lite/validation/validator/predicate/foreign/engine.rb +27 -0
  84. data/lib/lite/validation/validator/predicate/foreign/variant.rb +33 -0
  85. data/lib/lite/validation/validator/predicate/foreign/variants.rb +46 -0
  86. data/lib/lite/validation/validator/predicate/native/builder.rb +72 -0
  87. data/lib/lite/validation/validator/predicate/native/definite.rb +19 -0
  88. data/lib/lite/validation/validator/predicate/native/instance.rb +41 -0
  89. data/lib/lite/validation/validator/predicate/native/optional.rb +34 -0
  90. data/lib/lite/validation/validator/predicate/registry.rb +47 -0
  91. data/lib/lite/validation/validator/predicate.rb +17 -0
  92. data/lib/lite/validation/validator/result/abstract/failure.rb +21 -0
  93. data/lib/lite/validation/validator/result/abstract/instance.rb +18 -0
  94. data/lib/lite/validation/validator/result/abstract/success.rb +17 -0
  95. data/lib/lite/validation/validator/result/abstract.rb +29 -0
  96. data/lib/lite/validation/validator/result/committed.rb +75 -0
  97. data/lib/lite/validation/validator/result/disputable/hash.rb +17 -0
  98. data/lib/lite/validation/validator/result/disputable/instance.rb +43 -0
  99. data/lib/lite/validation/validator/result/disputable/iterable/array.rb +23 -0
  100. data/lib/lite/validation/validator/result/disputable/iterable.rb +17 -0
  101. data/lib/lite/validation/validator/result/disputable/navigable.rb +35 -0
  102. data/lib/lite/validation/validator/result/disputable.rb +14 -0
  103. data/lib/lite/validation/validator/result/disputed/abstract/hash.rb +32 -0
  104. data/lib/lite/validation/validator/result/disputed/abstract/instance.rb +26 -0
  105. data/lib/lite/validation/validator/result/disputed/iterable/array.rb +42 -0
  106. data/lib/lite/validation/validator/result/disputed/iterable/hash.rb +38 -0
  107. data/lib/lite/validation/validator/result/disputed/iterable.rb +20 -0
  108. data/lib/lite/validation/validator/result/disputed/navigable.rb +59 -0
  109. data/lib/lite/validation/validator/result/disputed.rb +17 -0
  110. data/lib/lite/validation/validator/result/refuted.rb +78 -0
  111. data/lib/lite/validation/validator/result/valid/abstract/collect.rb +42 -0
  112. data/lib/lite/validation/validator/result/valid/abstract/commit.rb +25 -0
  113. data/lib/lite/validation/validator/result/valid/abstract/instance.rb +23 -0
  114. data/lib/lite/validation/validator/result/valid/iterable/array/abstract.rb +24 -0
  115. data/lib/lite/validation/validator/result/valid/iterable/array/tuples.rb +64 -0
  116. data/lib/lite/validation/validator/result/valid/iterable/array/values.rb +42 -0
  117. data/lib/lite/validation/validator/result/valid/iterable/hash.rb +46 -0
  118. data/lib/lite/validation/validator/result/valid/iterable.rb +33 -0
  119. data/lib/lite/validation/validator/result/valid/navigable.rb +68 -0
  120. data/lib/lite/validation/validator/result/valid.rb +21 -0
  121. data/lib/lite/validation/validator/result.rb +15 -0
  122. data/lib/lite/validation/validator/ruling/abstract/invalid.rb +59 -0
  123. data/lib/lite/validation/validator/ruling/abstract/valid.rb +23 -0
  124. data/lib/lite/validation/validator/ruling/abstract.rb +12 -0
  125. data/lib/lite/validation/validator/ruling/commit.rb +17 -0
  126. data/lib/lite/validation/validator/ruling/dispute.rb +21 -0
  127. data/lib/lite/validation/validator/ruling/invalidate.rb +32 -0
  128. data/lib/lite/validation/validator/ruling/pass.rb +19 -0
  129. data/lib/lite/validation/validator/ruling/refute.rb +21 -0
  130. data/lib/lite/validation/validator/ruling.rb +53 -0
  131. data/lib/lite/validation/validator/state/instance.rb +46 -0
  132. data/lib/lite/validation/validator/state/merge_strategy.rb +50 -0
  133. data/lib/lite/validation/validator/state/unwrap_strategy.rb +31 -0
  134. data/lib/lite/validation/validator/state.rb +21 -0
  135. data/lib/lite/validation/validator.rb +15 -0
  136. data/lib/lite/validation/version.rb +9 -0
  137. data/lib/lite/validation.rb +8 -0
  138. 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,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'dry/engine'
@@ -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,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'coordinator/builder'
4
+
5
+ module Lite
6
+ module Validation
7
+ module Validator
8
+ module Coordinator
9
+ end
10
+ end
11
+ end
12
+ 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