treaty 0.0.1 → 0.2.0
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 +4 -4
- data/README.md +106 -17
- data/Rakefile +4 -2
- data/config/locales/en.yml +96 -0
- data/lib/treaty/attribute/base.rb +174 -0
- data/lib/treaty/attribute/builder/base.rb +143 -0
- data/lib/treaty/attribute/collection.rb +65 -0
- data/lib/treaty/attribute/helper_mapper.rb +72 -0
- data/lib/treaty/attribute/option/base.rb +160 -0
- data/lib/treaty/attribute/option/modifiers/as_modifier.rb +88 -0
- data/lib/treaty/attribute/option/modifiers/default_modifier.rb +103 -0
- data/lib/treaty/attribute/option/registry.rb +128 -0
- data/lib/treaty/attribute/option/registry_initializer.rb +90 -0
- data/lib/treaty/attribute/option/validators/inclusion_validator.rb +80 -0
- data/lib/treaty/attribute/option/validators/required_validator.rb +92 -0
- data/lib/treaty/attribute/option/validators/type_validator.rb +159 -0
- data/lib/treaty/attribute/option_normalizer.rb +151 -0
- data/lib/treaty/attribute/option_orchestrator.rb +187 -0
- data/lib/treaty/attribute/validation/attribute_validator.rb +144 -0
- data/lib/treaty/attribute/validation/base.rb +92 -0
- data/lib/treaty/attribute/validation/nested_array_validator.rb +199 -0
- data/lib/treaty/attribute/validation/nested_object_validator.rb +103 -0
- data/lib/treaty/attribute/validation/nested_transformer.rb +246 -0
- data/lib/treaty/attribute/validation/orchestrator/base.rb +194 -0
- data/lib/treaty/base.rb +9 -0
- data/lib/treaty/configuration.rb +17 -0
- data/lib/treaty/context/callable.rb +24 -0
- data/lib/treaty/context/dsl.rb +12 -0
- data/lib/treaty/context/workspace.rb +28 -0
- data/lib/treaty/controller/dsl.rb +38 -0
- data/lib/treaty/engine.rb +37 -0
- data/lib/treaty/exceptions/base.rb +47 -0
- data/lib/treaty/exceptions/class_name.rb +50 -0
- data/lib/treaty/exceptions/deprecated.rb +54 -0
- data/lib/treaty/exceptions/execution.rb +66 -0
- data/lib/treaty/exceptions/method_name.rb +55 -0
- data/lib/treaty/exceptions/nested_attributes.rb +65 -0
- data/lib/treaty/exceptions/not_implemented.rb +32 -0
- data/lib/treaty/exceptions/strategy.rb +63 -0
- data/lib/treaty/exceptions/unexpected.rb +70 -0
- data/lib/treaty/exceptions/validation.rb +97 -0
- data/lib/treaty/info/builder.rb +122 -0
- data/lib/treaty/info/dsl.rb +26 -0
- data/lib/treaty/info/result.rb +13 -0
- data/lib/treaty/request/attribute/attribute.rb +24 -0
- data/lib/treaty/request/attribute/builder.rb +22 -0
- data/lib/treaty/request/attribute/validation/orchestrator.rb +27 -0
- data/lib/treaty/request/attribute/validator.rb +50 -0
- data/lib/treaty/request/factory.rb +32 -0
- data/lib/treaty/request/scope/collection.rb +21 -0
- data/lib/treaty/request/scope/factory.rb +42 -0
- data/lib/treaty/response/attribute/attribute.rb +24 -0
- data/lib/treaty/response/attribute/builder.rb +22 -0
- data/lib/treaty/response/attribute/validation/orchestrator.rb +27 -0
- data/lib/treaty/response/attribute/validator.rb +44 -0
- data/lib/treaty/response/factory.rb +38 -0
- data/lib/treaty/response/scope/collection.rb +21 -0
- data/lib/treaty/response/scope/factory.rb +42 -0
- data/lib/treaty/result.rb +22 -0
- data/lib/treaty/strategy.rb +31 -0
- data/lib/treaty/support/loader.rb +24 -0
- data/lib/treaty/version.rb +8 -1
- data/lib/treaty/versions/collection.rb +15 -0
- data/lib/treaty/versions/dsl.rb +30 -0
- data/lib/treaty/versions/execution/request.rb +147 -0
- data/lib/treaty/versions/executor.rb +14 -0
- data/lib/treaty/versions/factory.rb +92 -0
- data/lib/treaty/versions/resolver.rb +69 -0
- data/lib/treaty/versions/semantic.rb +22 -0
- data/lib/treaty/versions/workspace.rb +40 -0
- data/lib/treaty.rb +3 -3
- metadata +200 -27
- data/.standard.yml +0 -3
- data/CHANGELOG.md +0 -5
- data/CODE_OF_CONDUCT.md +0 -84
- data/LICENSE.txt +0 -21
- data/sig/treaty.rbs +0 -4
- data/treaty.gemspec +0 -35
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
module Attribute
|
|
5
|
+
module Validation
|
|
6
|
+
# Validates and transforms individual attributes.
|
|
7
|
+
#
|
|
8
|
+
# ## Purpose
|
|
9
|
+
#
|
|
10
|
+
# Acts as the main interface for attribute validation and transformation.
|
|
11
|
+
# Delegates option processing to OptionOrchestrator and handles nested validation.
|
|
12
|
+
#
|
|
13
|
+
# ## Responsibilities
|
|
14
|
+
#
|
|
15
|
+
# 1. **Schema Validation** - Validates DSL definition correctness
|
|
16
|
+
# 2. **Value Validation** - Validates runtime data values
|
|
17
|
+
# 3. **Value Transformation** - Transforms values (defaults, etc.)
|
|
18
|
+
# 4. **Name Transformation** - Provides target name (for `as:` option)
|
|
19
|
+
# 5. **Nested Validation** - Delegates to NestedObjectValidator/NestedArrayValidator
|
|
20
|
+
#
|
|
21
|
+
# ## Usage
|
|
22
|
+
#
|
|
23
|
+
# Used by Orchestrator to validate each attribute:
|
|
24
|
+
#
|
|
25
|
+
# validator = AttributeValidator.new(attribute)
|
|
26
|
+
# validator.validate_schema!
|
|
27
|
+
# validator.validate_value!(value)
|
|
28
|
+
# transformed = validator.transform_value(value)
|
|
29
|
+
# target_name = validator.target_name
|
|
30
|
+
#
|
|
31
|
+
# ## Architecture
|
|
32
|
+
#
|
|
33
|
+
# Delegates to:
|
|
34
|
+
# - `OptionOrchestrator` - Coordinates all option processors
|
|
35
|
+
# - `NestedObjectValidator` - Validates nested object structures
|
|
36
|
+
# - `NestedArrayValidator` - Validates nested array structures
|
|
37
|
+
class AttributeValidator
|
|
38
|
+
attr_reader :attribute, :option_orchestrator
|
|
39
|
+
|
|
40
|
+
# Creates a new attribute validator instance
|
|
41
|
+
#
|
|
42
|
+
# @param attribute [Attribute::Base] The attribute to validate
|
|
43
|
+
def initialize(attribute)
|
|
44
|
+
@attribute = attribute
|
|
45
|
+
@option_orchestrator = OptionOrchestrator.new(attribute)
|
|
46
|
+
@nested_object_validator = nil
|
|
47
|
+
@nested_array_validator = nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Validates the attribute schema (DSL definition)
|
|
51
|
+
#
|
|
52
|
+
# @raise [Treaty::Exceptions::Validation] If schema is invalid
|
|
53
|
+
# @return [void]
|
|
54
|
+
def validate_schema!
|
|
55
|
+
option_orchestrator.validate_schema!
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Validates attribute value against all constraints
|
|
59
|
+
#
|
|
60
|
+
# @param value [Object] The value to validate
|
|
61
|
+
# @raise [Treaty::Exceptions::Validation] If validation fails
|
|
62
|
+
# @return [void]
|
|
63
|
+
def validate_value!(value)
|
|
64
|
+
option_orchestrator.validate_value!(value)
|
|
65
|
+
validate_nested!(value) if attribute.nested? && !value.nil?
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Transforms attribute value through all modifiers
|
|
69
|
+
#
|
|
70
|
+
# @param value [Object] The value to transform
|
|
71
|
+
# @return [Object] Transformed value
|
|
72
|
+
def transform_value(value)
|
|
73
|
+
option_orchestrator.transform_value(value)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Checks if attribute name is transformed
|
|
77
|
+
#
|
|
78
|
+
# @return [Boolean] True if name is transformed (as: option)
|
|
79
|
+
def transforms_name?
|
|
80
|
+
option_orchestrator.transforms_name?
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Gets the target attribute name
|
|
84
|
+
#
|
|
85
|
+
# @return [Symbol] The target name (or original if not transformed)
|
|
86
|
+
def target_name
|
|
87
|
+
option_orchestrator.target_name
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Validates only the type constraint
|
|
91
|
+
# Used by nested transformers to validate types before nested validation
|
|
92
|
+
#
|
|
93
|
+
# @param value [Object] The value to validate
|
|
94
|
+
# @raise [Treaty::Exceptions::Validation] If type validation fails
|
|
95
|
+
# @return [void]
|
|
96
|
+
def validate_type!(value)
|
|
97
|
+
type_processor = option_orchestrator.processor_for(:type)
|
|
98
|
+
type_processor&.validate_value!(value)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Validates only the required constraint
|
|
102
|
+
# Used by nested transformers to validate presence before nested validation
|
|
103
|
+
#
|
|
104
|
+
# @param value [Object] The value to validate
|
|
105
|
+
# @raise [Treaty::Exceptions::Validation] If required validation fails
|
|
106
|
+
# @return [void]
|
|
107
|
+
def validate_required!(value)
|
|
108
|
+
required_processor = option_orchestrator.processor_for(:required)
|
|
109
|
+
required_processor&.validate_value!(value) if attribute.options.key?(:required)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
# Validates nested attributes for object/array types
|
|
115
|
+
#
|
|
116
|
+
# @param value [Object] The value to validate
|
|
117
|
+
# @raise [Treaty::Exceptions::Validation] If nested validation fails
|
|
118
|
+
# @return [void]
|
|
119
|
+
def validate_nested!(value)
|
|
120
|
+
case attribute.type
|
|
121
|
+
when :object
|
|
122
|
+
nested_object_validator.validate!(value)
|
|
123
|
+
when :array
|
|
124
|
+
nested_array_validator.validate!(value)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Gets or creates nested object validator
|
|
129
|
+
#
|
|
130
|
+
# @return [NestedObjectValidator] Validator for nested objects
|
|
131
|
+
def nested_object_validator
|
|
132
|
+
@nested_object_validator ||= NestedObjectValidator.new(attribute)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Gets or creates nested array validator
|
|
136
|
+
#
|
|
137
|
+
# @return [NestedArrayValidator] Validator for nested arrays
|
|
138
|
+
def nested_array_validator
|
|
139
|
+
@nested_array_validator ||= NestedArrayValidator.new(attribute)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
module Attribute
|
|
5
|
+
module Validation
|
|
6
|
+
# Base class for validation strategies (adapter vs non-adapter).
|
|
7
|
+
#
|
|
8
|
+
# ## Purpose
|
|
9
|
+
#
|
|
10
|
+
# Provides common interface for validation strategies used in Treaty.
|
|
11
|
+
# Subclasses implement specific validation logic for different strategies.
|
|
12
|
+
#
|
|
13
|
+
# ## Responsibilities
|
|
14
|
+
#
|
|
15
|
+
# 1. **Strategy Interface** - Defines common validation interface
|
|
16
|
+
# 2. **Factory Pattern** - Provides class-level validate! method
|
|
17
|
+
# 3. **Strategy Detection** - Checks if adapter strategy is active
|
|
18
|
+
#
|
|
19
|
+
# ## Subclasses
|
|
20
|
+
#
|
|
21
|
+
# - Request::Validation - Validates request data (uses Orchestrator::Request)
|
|
22
|
+
# - Response::Validation - Validates response data (uses Orchestrator::Response)
|
|
23
|
+
#
|
|
24
|
+
# ## Usage
|
|
25
|
+
#
|
|
26
|
+
# Subclasses must implement:
|
|
27
|
+
# - `validate!` - Performs validation and returns transformed data
|
|
28
|
+
#
|
|
29
|
+
# Example usage:
|
|
30
|
+
# Request::Validation.validate!(version_factory: factory, data: params)
|
|
31
|
+
#
|
|
32
|
+
# ## Strategy Pattern
|
|
33
|
+
#
|
|
34
|
+
# The validation system supports two strategies:
|
|
35
|
+
# - **Adapter Strategy** - Adapts between different API versions
|
|
36
|
+
# - **Standard Strategy** - Direct version handling
|
|
37
|
+
#
|
|
38
|
+
# This base class provides `adapter_strategy?` helper to check current strategy.
|
|
39
|
+
#
|
|
40
|
+
# ## Factory Method
|
|
41
|
+
#
|
|
42
|
+
# The `self.validate!(...)` class method provides a convenient factory pattern:
|
|
43
|
+
# ```ruby
|
|
44
|
+
# Request::Validation.validate!(version_factory: factory, data: params)
|
|
45
|
+
# # Equivalent to:
|
|
46
|
+
# Request::Validation.new(version_factory: factory).validate!(data: params)
|
|
47
|
+
# ```
|
|
48
|
+
#
|
|
49
|
+
# ## Architecture
|
|
50
|
+
#
|
|
51
|
+
# Works with:
|
|
52
|
+
# - VersionFactory - Provides version and strategy information
|
|
53
|
+
# - Orchestrator::Base - Performs actual validation and transformation
|
|
54
|
+
class Base
|
|
55
|
+
# Class-level factory method for validation
|
|
56
|
+
# Creates instance and calls validate!
|
|
57
|
+
#
|
|
58
|
+
# @param args [Hash] Arguments passed to initialize and validate!
|
|
59
|
+
# @return [Hash] Validated and transformed data
|
|
60
|
+
def self.validate!(...)
|
|
61
|
+
new(...).validate!
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Creates a new validation instance
|
|
65
|
+
#
|
|
66
|
+
# @param version_factory [VersionFactory] Factory containing version and strategy
|
|
67
|
+
def initialize(version_factory:)
|
|
68
|
+
@version_factory = version_factory
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Performs validation and transformation
|
|
72
|
+
# Must be implemented in subclasses
|
|
73
|
+
#
|
|
74
|
+
# @raise [Treaty::Exceptions::NotImplemented] If subclass doesn't implement
|
|
75
|
+
# @return [Hash] Validated and transformed data
|
|
76
|
+
def validate!
|
|
77
|
+
raise Treaty::Exceptions::Validation,
|
|
78
|
+
I18n.t("treaty.attributes.validators.nested.orchestrator.collection_not_implemented")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
# Checks if adapter strategy is active
|
|
84
|
+
#
|
|
85
|
+
# @return [Boolean] True if using adapter strategy
|
|
86
|
+
def adapter_strategy?
|
|
87
|
+
@version_factory.strategy_instance.adapter?
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
module Attribute
|
|
5
|
+
module Validation
|
|
6
|
+
# Validates array elements against nested attribute definitions.
|
|
7
|
+
#
|
|
8
|
+
# ## Purpose
|
|
9
|
+
#
|
|
10
|
+
# Performs validation for nested array attributes during the validation phase.
|
|
11
|
+
# Handles both simple arrays (with :_self scope) and complex arrays (objects).
|
|
12
|
+
#
|
|
13
|
+
# ## Responsibilities
|
|
14
|
+
#
|
|
15
|
+
# 1. **Simple Array Validation** - Validates primitive values in arrays
|
|
16
|
+
# 2. **Complex Array Validation** - Validates hash objects within arrays
|
|
17
|
+
# 3. **Error Context** - Provides clear error messages with array index
|
|
18
|
+
# 4. **Type Checking** - Ensures elements match expected types
|
|
19
|
+
#
|
|
20
|
+
# ## Array Types
|
|
21
|
+
#
|
|
22
|
+
# ### Simple Array (`:_self` scope)
|
|
23
|
+
# Array containing primitive values (strings, integers, etc.)
|
|
24
|
+
#
|
|
25
|
+
# ```ruby
|
|
26
|
+
# array :tags do
|
|
27
|
+
# string :_self # Array of strings
|
|
28
|
+
# end
|
|
29
|
+
# ```
|
|
30
|
+
#
|
|
31
|
+
# Validates: `["ruby", "rails", "api"]`
|
|
32
|
+
#
|
|
33
|
+
# ### Complex Array (regular attributes)
|
|
34
|
+
# Array containing hash objects with defined structure
|
|
35
|
+
#
|
|
36
|
+
# ```ruby
|
|
37
|
+
# array :authors do
|
|
38
|
+
# string :name, :required
|
|
39
|
+
# string :email
|
|
40
|
+
# end
|
|
41
|
+
# ```
|
|
42
|
+
#
|
|
43
|
+
# Validates: `[{ name: "Alice", email: "alice@example.com" }, ...]`
|
|
44
|
+
#
|
|
45
|
+
# ## Usage
|
|
46
|
+
#
|
|
47
|
+
# Called by AttributeValidator for nested arrays:
|
|
48
|
+
#
|
|
49
|
+
# validator = NestedArrayValidator.new(attribute)
|
|
50
|
+
# validator.validate!(array_value)
|
|
51
|
+
#
|
|
52
|
+
# ## Error Handling
|
|
53
|
+
#
|
|
54
|
+
# Provides contextual error messages including:
|
|
55
|
+
# - Array attribute name
|
|
56
|
+
# - Element index (0-based)
|
|
57
|
+
# - Specific validation error
|
|
58
|
+
#
|
|
59
|
+
# Example error:
|
|
60
|
+
# "Error in array 'tags' at index 2: Element must match one of the defined types"
|
|
61
|
+
#
|
|
62
|
+
# ## Architecture
|
|
63
|
+
#
|
|
64
|
+
# Uses:
|
|
65
|
+
# - `AttributeValidator` - Validates individual elements
|
|
66
|
+
# - Caches validators for performance
|
|
67
|
+
# - Separates self validators from regular validators
|
|
68
|
+
class NestedArrayValidator
|
|
69
|
+
# Creates a new nested array validator
|
|
70
|
+
#
|
|
71
|
+
# @param attribute [Attribute::Base] The array-type attribute with nested attributes
|
|
72
|
+
def initialize(attribute)
|
|
73
|
+
@attribute = attribute
|
|
74
|
+
@self_validators = nil
|
|
75
|
+
@regular_validators = nil
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Validates all items in an array
|
|
79
|
+
# Skips validation if value is not an Array
|
|
80
|
+
#
|
|
81
|
+
# @param array [Array] The array to validate
|
|
82
|
+
# @raise [Treaty::Exceptions::Validation] If any item validation fails
|
|
83
|
+
# @return [void]
|
|
84
|
+
def validate!(array)
|
|
85
|
+
return unless array.is_a?(Array)
|
|
86
|
+
|
|
87
|
+
array.each_with_index do |array_item, index|
|
|
88
|
+
validate_self_array_item!(array_item, index) if self_validators.any?
|
|
89
|
+
|
|
90
|
+
validate_regular_array_item!(array_item, index) if regular_validators.any?
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
private
|
|
95
|
+
|
|
96
|
+
# Validates array item for simple arrays (with :_self scope)
|
|
97
|
+
# Simple array contains primitive values: strings, integers, datetimes, etc.
|
|
98
|
+
# Example: ["ruby", "rails", "api"] where each item is a String
|
|
99
|
+
#
|
|
100
|
+
# @param array_item [String, Integer, DateTime] Primitive value from simple array
|
|
101
|
+
# @param index [Integer] Array index for error messages
|
|
102
|
+
# @raise [Treaty::Exceptions::Validation] If primitive value doesn't match defined type
|
|
103
|
+
# @return [void]
|
|
104
|
+
def validate_self_array_item!(array_item, index) # rubocop:disable Metrics/MethodLength
|
|
105
|
+
errors = []
|
|
106
|
+
|
|
107
|
+
validated = self_validators.any? do |validator|
|
|
108
|
+
validator.validate_value!(array_item)
|
|
109
|
+
true
|
|
110
|
+
rescue Treaty::Exceptions::Validation => e
|
|
111
|
+
errors << e.message
|
|
112
|
+
false
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
return if validated
|
|
116
|
+
|
|
117
|
+
raise Treaty::Exceptions::Validation,
|
|
118
|
+
I18n.t("treaty.attributes.validators.nested.array.element_validation_error",
|
|
119
|
+
attribute: @attribute.name,
|
|
120
|
+
index:,
|
|
121
|
+
errors: errors.join("; "))
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Validates array item for complex arrays (with regular attributes)
|
|
125
|
+
# Complex array contains hash objects with defined structure
|
|
126
|
+
# Example: [{ name: "Alice", email: "alice@example.com" }, ...] where each item is a Hash
|
|
127
|
+
#
|
|
128
|
+
# @param array_item [Hash] Hash object from complex array
|
|
129
|
+
# @param index [Integer] Array index for error messages
|
|
130
|
+
# @raise [Treaty::Exceptions::Validation] If item is not Hash or nested validation fails
|
|
131
|
+
# @return [void]
|
|
132
|
+
def validate_regular_array_item!(array_item, index) # rubocop:disable Metrics/MethodLength
|
|
133
|
+
unless array_item.is_a?(Hash)
|
|
134
|
+
raise Treaty::Exceptions::Validation,
|
|
135
|
+
I18n.t("treaty.attributes.validators.nested.array.element_type_error",
|
|
136
|
+
attribute: @attribute.name,
|
|
137
|
+
index:,
|
|
138
|
+
actual: array_item.class)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
regular_validators.each do |nested_attribute, validator|
|
|
142
|
+
nested_value = array_item.fetch(nested_attribute.name, nil)
|
|
143
|
+
validator.validate_value!(nested_value)
|
|
144
|
+
rescue Treaty::Exceptions::Validation => e
|
|
145
|
+
raise Treaty::Exceptions::Validation,
|
|
146
|
+
I18n.t("treaty.attributes.validators.nested.array.attribute_error",
|
|
147
|
+
attribute: @attribute.name,
|
|
148
|
+
index:,
|
|
149
|
+
message: e.message)
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
########################################################################
|
|
154
|
+
|
|
155
|
+
# Gets cached self validators or builds them
|
|
156
|
+
#
|
|
157
|
+
# @return [Array<AttributeValidator>] Validators for :_self attributes
|
|
158
|
+
def self_validators
|
|
159
|
+
@self_validators ||= build_self_validators
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Gets cached regular validators or builds them
|
|
163
|
+
#
|
|
164
|
+
# @return [Hash] Hash of nested_attribute => validator
|
|
165
|
+
def regular_validators
|
|
166
|
+
@regular_validators ||= build_regular_validators
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
########################################################################
|
|
170
|
+
|
|
171
|
+
# Builds validators for :_self attributes (simple array elements)
|
|
172
|
+
#
|
|
173
|
+
# @return [Array<AttributeValidator>] Array of validators
|
|
174
|
+
def build_self_validators
|
|
175
|
+
@attribute.collection_of_attributes
|
|
176
|
+
.select { |attr| attr.name == :_self }
|
|
177
|
+
.map do |self_attribute|
|
|
178
|
+
validator = AttributeValidator.new(self_attribute)
|
|
179
|
+
validator.validate_schema!
|
|
180
|
+
validator
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Builds validators for regular attributes (complex array elements)
|
|
185
|
+
#
|
|
186
|
+
# @return [Hash] Hash of nested_attribute => validator
|
|
187
|
+
def build_regular_validators
|
|
188
|
+
@attribute.collection_of_attributes
|
|
189
|
+
.reject { |attr| attr.name == :_self }
|
|
190
|
+
.each_with_object({}) do |nested_attribute, cache|
|
|
191
|
+
validator = AttributeValidator.new(nested_attribute)
|
|
192
|
+
validator.validate_schema!
|
|
193
|
+
cache[nested_attribute] = validator
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
module Attribute
|
|
5
|
+
module Validation
|
|
6
|
+
# Validates nested object (hash) attributes against their defined structure.
|
|
7
|
+
#
|
|
8
|
+
# ## Purpose
|
|
9
|
+
#
|
|
10
|
+
# Performs validation for nested object attributes during the validation phase.
|
|
11
|
+
# Ensures hash values conform to the nested attribute definitions.
|
|
12
|
+
#
|
|
13
|
+
# ## Responsibilities
|
|
14
|
+
#
|
|
15
|
+
# 1. **Structure Validation** - Validates hash structure matches definition
|
|
16
|
+
# 2. **Attribute Validation** - Validates each nested attribute's value
|
|
17
|
+
# 3. **Type Safety** - Ensures value is a Hash before validation
|
|
18
|
+
# 4. **Validator Caching** - Builds and caches validators for performance
|
|
19
|
+
#
|
|
20
|
+
# ## Usage
|
|
21
|
+
#
|
|
22
|
+
# Used for object-type attributes with nested definitions:
|
|
23
|
+
#
|
|
24
|
+
# ```ruby
|
|
25
|
+
# object :author do
|
|
26
|
+
# string :name, :required
|
|
27
|
+
# string :email
|
|
28
|
+
# integer :age
|
|
29
|
+
# end
|
|
30
|
+
# ```
|
|
31
|
+
#
|
|
32
|
+
# Validates: `{ name: "Alice", email: "alice@example.com", age: 30 }`
|
|
33
|
+
#
|
|
34
|
+
# ## Usage in Code
|
|
35
|
+
#
|
|
36
|
+
# Called by AttributeValidator for nested objects:
|
|
37
|
+
#
|
|
38
|
+
# validator = NestedObjectValidator.new(attribute)
|
|
39
|
+
# validator.validate!(hash_value)
|
|
40
|
+
#
|
|
41
|
+
# ## Validation Flow
|
|
42
|
+
#
|
|
43
|
+
# 1. Check if value is a Hash
|
|
44
|
+
# 2. Build validators for all nested attributes (cached)
|
|
45
|
+
# 3. For each nested attribute:
|
|
46
|
+
# - Extract value from hash
|
|
47
|
+
# - Validate using AttributeValidator
|
|
48
|
+
# 4. Raise exception if any validation fails
|
|
49
|
+
#
|
|
50
|
+
# ## Architecture
|
|
51
|
+
#
|
|
52
|
+
# Uses:
|
|
53
|
+
# - `AttributeValidator` - Validates individual nested attributes
|
|
54
|
+
# - Caches validators to avoid rebuilding on each validation
|
|
55
|
+
class NestedObjectValidator
|
|
56
|
+
attr_reader :attribute
|
|
57
|
+
|
|
58
|
+
# Creates a new nested object validator
|
|
59
|
+
#
|
|
60
|
+
# @param attribute [Attribute::Base] The object-type attribute with nested attributes
|
|
61
|
+
def initialize(attribute)
|
|
62
|
+
@attribute = attribute
|
|
63
|
+
@validators_cache = nil
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Validates all nested attributes in a hash
|
|
67
|
+
# Skips validation if value is not a Hash
|
|
68
|
+
#
|
|
69
|
+
# @param hash [Hash] The hash to validate
|
|
70
|
+
# @raise [Treaty::Exceptions::Validation] If any nested validation fails
|
|
71
|
+
# @return [void]
|
|
72
|
+
def validate!(hash)
|
|
73
|
+
return unless hash.is_a?(Hash)
|
|
74
|
+
|
|
75
|
+
validators.each do |nested_attribute, nested_validator|
|
|
76
|
+
nested_value = hash.fetch(nested_attribute.name, nil)
|
|
77
|
+
nested_validator.validate_value!(nested_value)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
# Gets cached validators or builds them
|
|
84
|
+
#
|
|
85
|
+
# @return [Hash] Hash of nested_attribute => validator
|
|
86
|
+
def validators
|
|
87
|
+
@validators ||= build_validators
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Builds validators for all nested attributes
|
|
91
|
+
#
|
|
92
|
+
# @return [Hash] Hash of nested_attribute => validator
|
|
93
|
+
def build_validators
|
|
94
|
+
attribute.collection_of_attributes.each_with_object({}) do |nested_attribute, cache|
|
|
95
|
+
validator = AttributeValidator.new(nested_attribute)
|
|
96
|
+
validator.validate_schema!
|
|
97
|
+
cache[nested_attribute] = validator
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|