treaty 0.17.0 → 0.19.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.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/config/locales/en.yml +6 -2
  4. data/lib/treaty/engine.rb +1 -1
  5. data/lib/treaty/{attribute/entity → entity/attribute}/attribute.rb +4 -4
  6. data/lib/treaty/entity/attribute/base.rb +184 -0
  7. data/lib/treaty/entity/attribute/builder/base.rb +275 -0
  8. data/lib/treaty/entity/attribute/collection.rb +67 -0
  9. data/lib/treaty/entity/attribute/dsl.rb +92 -0
  10. data/lib/treaty/entity/attribute/helper_mapper.rb +74 -0
  11. data/lib/treaty/entity/attribute/option/base.rb +190 -0
  12. data/lib/treaty/entity/attribute/option/conditionals/base.rb +92 -0
  13. data/lib/treaty/entity/attribute/option/conditionals/if_conditional.rb +136 -0
  14. data/lib/treaty/entity/attribute/option/conditionals/unless_conditional.rb +153 -0
  15. data/lib/treaty/entity/attribute/option/modifiers/as_modifier.rb +93 -0
  16. data/lib/treaty/entity/attribute/option/modifiers/cast_modifier.rb +285 -0
  17. data/lib/treaty/entity/attribute/option/modifiers/computed_modifier.rb +128 -0
  18. data/lib/treaty/entity/attribute/option/modifiers/default_modifier.rb +105 -0
  19. data/lib/treaty/entity/attribute/option/modifiers/transform_modifier.rb +114 -0
  20. data/lib/treaty/entity/attribute/option/registry.rb +157 -0
  21. data/lib/treaty/entity/attribute/option/registry_initializer.rb +117 -0
  22. data/lib/treaty/entity/attribute/option/validators/format_validator.rb +222 -0
  23. data/lib/treaty/entity/attribute/option/validators/inclusion_validator.rb +94 -0
  24. data/lib/treaty/entity/attribute/option/validators/required_validator.rb +100 -0
  25. data/lib/treaty/entity/attribute/option/validators/type_validator.rb +219 -0
  26. data/lib/treaty/entity/attribute/option_normalizer.rb +168 -0
  27. data/lib/treaty/entity/attribute/option_orchestrator.rb +192 -0
  28. data/lib/treaty/entity/attribute/validation/attribute_validator.rb +147 -0
  29. data/lib/treaty/entity/attribute/validation/base.rb +76 -0
  30. data/lib/treaty/entity/attribute/validation/nested_array_validator.rb +207 -0
  31. data/lib/treaty/entity/attribute/validation/nested_object_validator.rb +105 -0
  32. data/lib/treaty/entity/attribute/validation/nested_transformer.rb +432 -0
  33. data/lib/treaty/entity/attribute/validation/orchestrator/base.rb +262 -0
  34. data/lib/treaty/entity/base.rb +90 -0
  35. data/lib/treaty/entity/builder.rb +44 -0
  36. data/lib/treaty/{info/entity → entity/info}/builder.rb +8 -8
  37. data/lib/treaty/{info/entity → entity/info}/dsl.rb +2 -2
  38. data/lib/treaty/{info/entity → entity/info}/result.rb +2 -2
  39. data/lib/treaty/entity.rb +7 -75
  40. data/lib/treaty/request/attribute/attribute.rb +1 -1
  41. data/lib/treaty/request/attribute/builder.rb +24 -1
  42. data/lib/treaty/request/entity.rb +1 -1
  43. data/lib/treaty/request/factory.rb +6 -6
  44. data/lib/treaty/request/validator.rb +1 -1
  45. data/lib/treaty/response/attribute/attribute.rb +1 -1
  46. data/lib/treaty/response/attribute/builder.rb +24 -1
  47. data/lib/treaty/response/entity.rb +1 -1
  48. data/lib/treaty/response/factory.rb +6 -6
  49. data/lib/treaty/response/validator.rb +1 -1
  50. data/lib/treaty/version.rb +1 -1
  51. metadata +35 -34
  52. data/lib/treaty/attribute/base.rb +0 -182
  53. data/lib/treaty/attribute/builder/base.rb +0 -143
  54. data/lib/treaty/attribute/collection.rb +0 -65
  55. data/lib/treaty/attribute/dsl.rb +0 -90
  56. data/lib/treaty/attribute/entity/builder.rb +0 -23
  57. data/lib/treaty/attribute/helper_mapper.rb +0 -72
  58. data/lib/treaty/attribute/option/base.rb +0 -188
  59. data/lib/treaty/attribute/option/conditionals/base.rb +0 -90
  60. data/lib/treaty/attribute/option/conditionals/if_conditional.rb +0 -134
  61. data/lib/treaty/attribute/option/conditionals/unless_conditional.rb +0 -151
  62. data/lib/treaty/attribute/option/modifiers/as_modifier.rb +0 -91
  63. data/lib/treaty/attribute/option/modifiers/cast_modifier.rb +0 -283
  64. data/lib/treaty/attribute/option/modifiers/computed_modifier.rb +0 -126
  65. data/lib/treaty/attribute/option/modifiers/default_modifier.rb +0 -103
  66. data/lib/treaty/attribute/option/modifiers/transform_modifier.rb +0 -112
  67. data/lib/treaty/attribute/option/registry.rb +0 -155
  68. data/lib/treaty/attribute/option/registry_initializer.rb +0 -115
  69. data/lib/treaty/attribute/option/validators/format_validator.rb +0 -220
  70. data/lib/treaty/attribute/option/validators/inclusion_validator.rb +0 -92
  71. data/lib/treaty/attribute/option/validators/required_validator.rb +0 -98
  72. data/lib/treaty/attribute/option/validators/type_validator.rb +0 -217
  73. data/lib/treaty/attribute/option_normalizer.rb +0 -166
  74. data/lib/treaty/attribute/option_orchestrator.rb +0 -190
  75. data/lib/treaty/attribute/validation/attribute_validator.rb +0 -145
  76. data/lib/treaty/attribute/validation/base.rb +0 -74
  77. data/lib/treaty/attribute/validation/nested_array_validator.rb +0 -205
  78. data/lib/treaty/attribute/validation/nested_object_validator.rb +0 -103
  79. data/lib/treaty/attribute/validation/nested_transformer.rb +0 -430
  80. data/lib/treaty/attribute/validation/orchestrator/base.rb +0 -260
@@ -0,0 +1,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Treaty
4
+ module Entity
5
+ module Attribute
6
+ # Normalizes options from simple mode to advanced mode.
7
+ #
8
+ # ## Purpose
9
+ #
10
+ # All options are stored and processed internally in advanced mode.
11
+ # This normalizer converts simple mode to advanced mode automatically.
12
+ #
13
+ # ## Modes Explained
14
+ #
15
+ # ### Simple Mode (Concise syntax)
16
+ # ```ruby
17
+ # {
18
+ # required: true,
19
+ # as: :value,
20
+ # in: %w[twitter linkedin github],
21
+ # default: 12
22
+ # }
23
+ # ```
24
+ #
25
+ # ### Advanced Mode (With messages)
26
+ # ```ruby
27
+ # {
28
+ # required: { is: true, message: nil },
29
+ # as: { is: :value, message: nil },
30
+ # inclusion: { in: %w[twitter linkedin github], message: nil },
31
+ # default: { is: 12, message: nil }
32
+ # }
33
+ # ```
34
+ #
35
+ # ## Key Mappings
36
+ #
37
+ # Some simple mode keys are renamed in advanced mode:
38
+ # - `in:` → `inclusion:` (with value key `:in`)
39
+ #
40
+ # Others keep the same name:
41
+ # - `required:` → `required:` (with value key `:is`)
42
+ # - `as:` → `as:` (with value key `:is`)
43
+ # - `default:` → `default:` (with value key `:is`)
44
+ #
45
+ # ## Value Keys
46
+ #
47
+ # Each option has a value key in advanced mode:
48
+ # - Default: `:is` (most options)
49
+ # - Special: `:in` (inclusion validator)
50
+ #
51
+ # ## Message Field
52
+ #
53
+ # The `message` field in advanced mode allows custom error messages:
54
+ # - `nil` - Use default message (most common)
55
+ # - String - Custom error message for validation failures
56
+ #
57
+ # ## Usage in DSL
58
+ #
59
+ # Users can write in either mode:
60
+ #
61
+ # Simple mode:
62
+ # string :provider, in: %w[twitter linkedin]
63
+ #
64
+ # Advanced mode:
65
+ # string :provider, inclusion: { in: %w[twitter linkedin], message: "Invalid provider" }
66
+ #
67
+ # Both are normalized to advanced mode internally.
68
+ class OptionNormalizer
69
+ # Maps simple mode option keys to their advanced mode configuration.
70
+ # Format: simple_key => { advanced_key:, value_key: }
71
+ OPTION_KEY_MAPPING = {
72
+ in: { advanced_key: :inclusion, value_key: :in },
73
+ as: { advanced_key: :as, value_key: :is },
74
+ default: { advanced_key: :default, value_key: :is },
75
+ cast: { advanced_key: :cast, value_key: :to }
76
+ }.freeze
77
+ private_constant :OPTION_KEY_MAPPING
78
+
79
+ # Reverse mapping: advanced_key => value_key
80
+ # Used to determine value key when option is already in advanced mode.
81
+ ADVANCED_KEY_TO_VALUE_KEY = OPTION_KEY_MAPPING.each_with_object({}) do |(_, config), result|
82
+ result[config.fetch(:advanced_key)] = config.fetch(:value_key)
83
+ end.freeze
84
+ private_constant :ADVANCED_KEY_TO_VALUE_KEY
85
+
86
+ DEFAULT_VALUE_KEY = :is
87
+ private_constant :DEFAULT_VALUE_KEY
88
+
89
+ class << self
90
+ # Normalizes all options from simple mode to advanced mode
91
+ # and sorts them by position for consistent execution order.
92
+ #
93
+ # @param options [Hash] Options hash in simple or advanced mode
94
+ # @return [Hash] Normalized options in advanced mode, sorted by position
95
+ def normalize(options)
96
+ normalized = options.each_with_object({}) do |(key, value), result|
97
+ advanced_key, normalized_value = normalize_option(key, value)
98
+ result[advanced_key] = normalized_value
99
+ end
100
+
101
+ sort_by_position(normalized)
102
+ end
103
+
104
+ private
105
+
106
+ # Sorts options by their registered position.
107
+ # Options without position (like conditionals) sort first (position 0).
108
+ #
109
+ # @param options_hash [Hash] Normalized options hash
110
+ # @return [Hash] Options sorted by position
111
+ def sort_by_position(options_hash)
112
+ options_hash.sort_by do |option_name, _|
113
+ Option::Registry.position_for(option_name) || 0
114
+ end.to_h
115
+ end
116
+
117
+ # Normalizes a single option to advanced mode
118
+ #
119
+ # @param key [Symbol] Option key
120
+ # @param value [Object] Option value
121
+ # @return [Array<Symbol, Hash>] Tuple of [advanced_key, normalized_value]
122
+ def normalize_option(key, value) # rubocop:disable Metrics/MethodLength
123
+ mapping = OPTION_KEY_MAPPING.fetch(key, nil)
124
+
125
+ if mapping.present?
126
+ # Special handling for mapped options (e.g., in -> inclusion).
127
+ advanced_key = mapping.fetch(:advanced_key)
128
+ value_key = mapping.fetch(:value_key)
129
+ normalized_value = normalize_value(value, value_key)
130
+ [advanced_key, normalized_value]
131
+ else
132
+ # Check if this key is already an advanced mode key.
133
+ value_key = ADVANCED_KEY_TO_VALUE_KEY.fetch(key, nil) || DEFAULT_VALUE_KEY
134
+ normalized_value = normalize_value(value, value_key)
135
+ [key, normalized_value]
136
+ end
137
+ end
138
+
139
+ # Normalizes option value to advanced mode format
140
+ #
141
+ # @param value [Object] The option value (simple or advanced mode)
142
+ # @param value_key [Symbol] The key to use for the value (:is or :in)
143
+ # @return [Hash] Normalized hash with value_key and :message
144
+ def normalize_value(value, value_key)
145
+ if advanced_mode?(value, value_key)
146
+ # Already in advanced mode, ensure it has both keys.
147
+ # message: nil means use I18n default message from validators
148
+ { value_key => value.fetch(value_key), message: value.fetch(:message, nil) }
149
+ else
150
+ # Simple mode, convert to advanced.
151
+ # message: nil means use I18n default message from validators
152
+ { value_key => value, message: nil }
153
+ end
154
+ end
155
+
156
+ # Checks if value is already in advanced mode
157
+ #
158
+ # @param value [Object] The value to check
159
+ # @param value_key [Symbol] The expected value key
160
+ # @return [Boolean] True if value is a hash with the value key
161
+ def advanced_mode?(value, value_key)
162
+ value.is_a?(Hash) && value.key?(value_key)
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,192 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Treaty
4
+ module Entity
5
+ module Attribute
6
+ # Orchestrates all option processors for a single attribute.
7
+ #
8
+ # ## Purpose
9
+ #
10
+ # Coordinates the execution of all option processors (validators and modifiers)
11
+ # for an attribute through three distinct processing phases.
12
+ #
13
+ # ## Responsibilities
14
+ #
15
+ # 1. **Processor Building** - Creates instances of all relevant option processors
16
+ # 2. **Schema Validation** - Validates DSL definition correctness (phase 1)
17
+ # 3. **Value Validation** - Validates runtime data values (phase 2)
18
+ # 4. **Value Transformation** - Transforms values through modifiers (phase 3)
19
+ # 5. **Name Transformation** - Provides target name if `as:` option is used
20
+ #
21
+ # ## Processing Phases
22
+ #
23
+ # ### Phase 1: Schema Validation
24
+ # Validates that the attribute definition in the DSL is correct.
25
+ # Called once during treaty definition loading.
26
+ #
27
+ # ```ruby
28
+ # orchestrator.validate_schema!
29
+ # ```
30
+ #
31
+ # ### Phase 2: Value Validation
32
+ # Validates that runtime data matches the constraints.
33
+ # Called for each request/response.
34
+ #
35
+ # ```ruby
36
+ # orchestrator.validate_value!(value)
37
+ # ```
38
+ #
39
+ # ### Phase 3: Value Transformation
40
+ # Transforms the value (applies defaults, renaming, etc.).
41
+ # Called for each request/response after validation.
42
+ #
43
+ # ```ruby
44
+ # transformed = orchestrator.transform_value(value)
45
+ # ```
46
+ #
47
+ # ## Usage
48
+ #
49
+ # Used by AttributeValidator to coordinate all option processing:
50
+ #
51
+ # orchestrator = OptionOrchestrator.new(attribute)
52
+ # orchestrator.validate_schema!
53
+ # orchestrator.validate_value!(value)
54
+ # transformed = orchestrator.transform_value(value)
55
+ # target_name = orchestrator.target_name
56
+ #
57
+ # ## Processor Building
58
+ #
59
+ # Automatically:
60
+ # - Builds processor instances for all defined options
61
+ # - Always includes TypeValidator (even if not explicitly defined)
62
+ # - Validates that all options are registered in Registry
63
+ # - Raises error for unknown options
64
+ #
65
+ # ## Architecture
66
+ #
67
+ # Works with:
68
+ # - Option::Registry - Looks up processor classes
69
+ # - Option::Base - Base class for all processors
70
+ # - AttributeValidator - Uses orchestrator to coordinate processing
71
+ class OptionOrchestrator
72
+ # Creates a new orchestrator instance
73
+ #
74
+ # @param attribute [Attribute::Base] The attribute to orchestrate options for
75
+ def initialize(attribute)
76
+ @attribute = attribute
77
+ @processors = build_processors
78
+ end
79
+
80
+ # Phase 1: Validates all option schemas
81
+ # Ensures DSL definition is correct and all options are registered
82
+ #
83
+ # @raise [Treaty::Exceptions::Validation] If unknown options found
84
+ # @return [void]
85
+ def validate_schema!
86
+ validate_known_options!
87
+
88
+ @processors.each_value(&:validate_schema!)
89
+ end
90
+
91
+ # Phase 2: Validates value against all option validators
92
+ # Validates runtime data against all defined constraints
93
+ #
94
+ # @param value [Object] The value to validate
95
+ # @raise [Treaty::Exceptions::Validation] If validation fails
96
+ # @return [void]
97
+ def validate_value!(value)
98
+ @processors.each_value do |processor|
99
+ processor.validate_value!(value)
100
+ end
101
+ end
102
+
103
+ # Phase 3: Transforms value through all option modifiers
104
+ # Applies transformations like defaults, type coercion, etc.
105
+ #
106
+ # @param value [Object] The value to transform
107
+ # @param root_data [Hash] Full raw data from root level (used by computed modifier)
108
+ # @return [Object] Transformed value
109
+ def transform_value(value, root_data = {})
110
+ @processors.values.reduce(value) do |current_value, processor|
111
+ processor.transform_value(current_value, root_data)
112
+ end
113
+ end
114
+
115
+ # Checks if any processor transforms the attribute name
116
+ #
117
+ # @return [Boolean] True if any processor (like AsModifier) transforms names
118
+ def transforms_name?
119
+ @processors.values.any?(&:transforms_name?)
120
+ end
121
+
122
+ # Gets the target name from the processor that transforms names
123
+ # Returns original name if no transformation
124
+ #
125
+ # @return [Symbol] The target attribute name
126
+ def target_name
127
+ name_transformer = @processors.values.find(&:transforms_name?)
128
+ name_transformer ? name_transformer.target_name : @attribute.name
129
+ end
130
+
131
+ # Gets specific processor by option name
132
+ #
133
+ # @param option_name [Symbol] The option name (:required, :type, etc.)
134
+ # @return [Option::Base, nil] The processor instance or nil if not found
135
+ def processor_for(option_name)
136
+ @processors.fetch(option_name)
137
+ end
138
+
139
+ private
140
+
141
+ # Builds processor instances for all defined options
142
+ # Always includes TypeValidator even if not explicitly defined
143
+ #
144
+ # @return [Hash<Symbol, Option::Base>] Hash of option_name => processor
145
+ def build_processors # rubocop:disable Metrics/MethodLength
146
+ processors_hash = {}
147
+
148
+ @attribute.options.each do |option_name, option_schema|
149
+ processor_class = Option::Registry.processor_for(option_name)
150
+
151
+ next if processor_class.nil?
152
+
153
+ processors_hash[option_name] = processor_class.new(
154
+ attribute_name: @attribute.name,
155
+ attribute_type: @attribute.type,
156
+ option_schema:
157
+ )
158
+ end
159
+
160
+ # Always include type validator
161
+ unless processors_hash.key?(:type)
162
+ processors_hash[:type] = Option::Validators::TypeValidator.new(
163
+ attribute_name: @attribute.name,
164
+ attribute_type: @attribute.type,
165
+ option_schema: nil
166
+ )
167
+ end
168
+
169
+ processors_hash
170
+ end
171
+
172
+ # Validates that all options are registered in the Registry
173
+ #
174
+ # @raise [Treaty::Exceptions::Validation] If unknown options found
175
+ # @return [void]
176
+ def validate_known_options!
177
+ unknown_options = @attribute.options.keys - Option::Registry.all_options
178
+
179
+ return if unknown_options.empty?
180
+
181
+ raise Treaty::Exceptions::Validation,
182
+ I18n.t(
183
+ "treaty.attributes.options.unknown",
184
+ attribute: @attribute.name,
185
+ unknown: unknown_options.join(", "),
186
+ known: Option::Registry.all_options.join(", ")
187
+ )
188
+ end
189
+ end
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Treaty
4
+ module Entity
5
+ module Attribute
6
+ module Validation
7
+ # Validates and transforms individual attributes.
8
+ #
9
+ # ## Purpose
10
+ #
11
+ # Acts as the main interface for attribute validation and transformation.
12
+ # Delegates option processing to OptionOrchestrator and handles nested validation.
13
+ #
14
+ # ## Responsibilities
15
+ #
16
+ # 1. **Schema Validation** - Validates DSL definition correctness
17
+ # 2. **Value Validation** - Validates runtime data values
18
+ # 3. **Value Transformation** - Transforms values (defaults, etc.)
19
+ # 4. **Name Transformation** - Provides target name (for `as:` option)
20
+ # 5. **Nested Validation** - Delegates to NestedObjectValidator/NestedArrayValidator
21
+ #
22
+ # ## Usage
23
+ #
24
+ # Used by Orchestrator to validate each attribute:
25
+ #
26
+ # validator = AttributeValidator.new(attribute)
27
+ # validator.validate_schema!
28
+ # validator.validate_value!(value)
29
+ # transformed = validator.transform_value(value)
30
+ # target_name = validator.target_name
31
+ #
32
+ # ## Architecture
33
+ #
34
+ # Delegates to:
35
+ # - `OptionOrchestrator` - Coordinates all option processors
36
+ # - `NestedObjectValidator` - Validates nested object structures
37
+ # - `NestedArrayValidator` - Validates nested array structures
38
+ class AttributeValidator
39
+ attr_reader :attribute, :option_orchestrator
40
+
41
+ # Creates a new attribute validator instance
42
+ #
43
+ # @param attribute [Attribute::Base] The attribute to validate
44
+ def initialize(attribute)
45
+ @attribute = attribute
46
+ @option_orchestrator = OptionOrchestrator.new(attribute)
47
+ @nested_object_validator = nil
48
+ @nested_array_validator = nil
49
+ end
50
+
51
+ # Validates the attribute schema (DSL definition)
52
+ #
53
+ # @raise [Treaty::Exceptions::Validation] If schema is invalid
54
+ # @return [void]
55
+ def validate_schema!
56
+ option_orchestrator.validate_schema!
57
+ end
58
+
59
+ # Validates attribute value against all constraints
60
+ #
61
+ # @param value [Object] The value to validate
62
+ # @raise [Treaty::Exceptions::Validation] If validation fails
63
+ # @return [void]
64
+ def validate_value!(value)
65
+ option_orchestrator.validate_value!(value)
66
+ validate_nested!(value) if attribute.nested? && !value.nil?
67
+ end
68
+
69
+ # Transforms attribute value through all modifiers
70
+ #
71
+ # @param value [Object] The value to transform
72
+ # @param root_data [Hash] Full raw data from root level (used by computed modifier)
73
+ # @return [Object] Transformed value
74
+ def transform_value(value, root_data = {})
75
+ option_orchestrator.transform_value(value, root_data)
76
+ end
77
+
78
+ # Checks if attribute name is transformed
79
+ #
80
+ # @return [Boolean] True if name is transformed (as: option)
81
+ def transforms_name?
82
+ option_orchestrator.transforms_name?
83
+ end
84
+
85
+ # Gets the target attribute name
86
+ #
87
+ # @return [Symbol] The target name (or original if not transformed)
88
+ def target_name
89
+ option_orchestrator.target_name
90
+ end
91
+
92
+ # Validates only the type constraint
93
+ # Used by nested transformers to validate types before nested validation
94
+ #
95
+ # @param value [Object] The value to validate
96
+ # @raise [Treaty::Exceptions::Validation] If type validation fails
97
+ # @return [void]
98
+ def validate_type!(value)
99
+ type_processor = option_orchestrator.processor_for(:type)
100
+ type_processor&.validate_value!(value)
101
+ end
102
+
103
+ # Validates only the required constraint
104
+ # Used by nested transformers to validate presence before nested validation
105
+ #
106
+ # @param value [Object] The value to validate
107
+ # @raise [Treaty::Exceptions::Validation] If required validation fails
108
+ # @return [void]
109
+ def validate_required!(value)
110
+ required_processor = option_orchestrator.processor_for(:required)
111
+ required_processor&.validate_value!(value) if attribute.options.key?(:required)
112
+ end
113
+
114
+ private
115
+
116
+ # Validates nested attributes for object/array types
117
+ #
118
+ # @param value [Object] The value to validate
119
+ # @raise [Treaty::Exceptions::Validation] If nested validation fails
120
+ # @return [void]
121
+ def validate_nested!(value)
122
+ case attribute.type
123
+ when :object
124
+ nested_object_validator.validate!(value)
125
+ when :array
126
+ nested_array_validator.validate!(value)
127
+ end
128
+ end
129
+
130
+ # Gets or creates nested object validator
131
+ #
132
+ # @return [NestedObjectValidator] Validator for nested objects
133
+ def nested_object_validator
134
+ @nested_object_validator ||= NestedObjectValidator.new(attribute)
135
+ end
136
+
137
+ # Gets or creates nested array validator
138
+ #
139
+ # @return [NestedArrayValidator] Validator for nested arrays
140
+ def nested_array_validator
141
+ @nested_array_validator ||= NestedArrayValidator.new(attribute)
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Treaty
4
+ module Entity
5
+ module Attribute
6
+ module Validation
7
+ # Base class for request and response validation.
8
+ #
9
+ # ## Purpose
10
+ #
11
+ # Provides common interface for validation used in Treaty.
12
+ # Subclasses implement specific validation logic for requests and responses.
13
+ #
14
+ # ## Responsibilities
15
+ #
16
+ # 1. **Validation Interface** - Defines common validation interface
17
+ # 2. **Factory Pattern** - Provides class-level validate! method
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
+ # ## Factory Method
33
+ #
34
+ # The `self.validate!(...)` class method provides a convenient factory pattern:
35
+ # ```ruby
36
+ # Request::Validation.validate!(version_factory: factory, data: params)
37
+ # # Equivalent to:
38
+ # Request::Validation.new(version_factory: factory).validate!(data: params)
39
+ # ```
40
+ #
41
+ # ## Architecture
42
+ #
43
+ # Works with:
44
+ # - VersionFactory - Provides version information
45
+ # - Orchestrator::Base - Performs actual validation and transformation
46
+ class Base
47
+ # Class-level factory method for validation
48
+ # Creates instance and calls validate!
49
+ #
50
+ # @param args [Hash] Arguments passed to initialize and validate!
51
+ # @return [Hash] Validated and transformed data
52
+ def self.validate!(...)
53
+ new(...).validate!
54
+ end
55
+
56
+ # Creates a new validation instance
57
+ #
58
+ # @param version_factory [VersionFactory] Factory containing version information
59
+ def initialize(version_factory:)
60
+ @version_factory = version_factory
61
+ end
62
+
63
+ # Performs validation and transformation
64
+ # Must be implemented in subclasses
65
+ #
66
+ # @raise [Treaty::Exceptions::NotImplemented] If subclass doesn't implement
67
+ # @return [Hash] Validated and transformed data
68
+ def validate!
69
+ raise Treaty::Exceptions::Validation,
70
+ I18n.t("treaty.attributes.validators.nested.orchestrator.collection_not_implemented")
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end