treaty 0.0.1 → 0.1.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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +19 -18
  3. data/Rakefile +4 -2
  4. data/lib/treaty/attribute/base.rb +172 -0
  5. data/lib/treaty/attribute/builder/base.rb +142 -0
  6. data/lib/treaty/attribute/collection.rb +65 -0
  7. data/lib/treaty/attribute/helper_mapper.rb +72 -0
  8. data/lib/treaty/attribute/option/base.rb +159 -0
  9. data/lib/treaty/attribute/option/modifiers/as_modifier.rb +87 -0
  10. data/lib/treaty/attribute/option/modifiers/default_modifier.rb +103 -0
  11. data/lib/treaty/attribute/option/registry.rb +128 -0
  12. data/lib/treaty/attribute/option/registry_initializer.rb +90 -0
  13. data/lib/treaty/attribute/option/validators/inclusion_validator.rb +80 -0
  14. data/lib/treaty/attribute/option/validators/required_validator.rb +94 -0
  15. data/lib/treaty/attribute/option/validators/type_validator.rb +153 -0
  16. data/lib/treaty/attribute/option_normalizer.rb +150 -0
  17. data/lib/treaty/attribute/option_orchestrator.rb +186 -0
  18. data/lib/treaty/attribute/validation/attribute_validator.rb +144 -0
  19. data/lib/treaty/attribute/validation/base.rb +93 -0
  20. data/lib/treaty/attribute/validation/nested_array_validator.rb +194 -0
  21. data/lib/treaty/attribute/validation/nested_object_validator.rb +103 -0
  22. data/lib/treaty/attribute/validation/nested_transformer.rb +240 -0
  23. data/lib/treaty/attribute/validation/orchestrator/base.rb +196 -0
  24. data/lib/treaty/base.rb +9 -0
  25. data/lib/treaty/configuration.rb +17 -0
  26. data/lib/treaty/context/callable.rb +24 -0
  27. data/lib/treaty/context/dsl.rb +12 -0
  28. data/lib/treaty/context/workspace.rb +28 -0
  29. data/lib/treaty/controller/dsl.rb +38 -0
  30. data/lib/treaty/engine.rb +37 -0
  31. data/lib/treaty/exceptions/base.rb +8 -0
  32. data/lib/treaty/exceptions/class_name.rb +11 -0
  33. data/lib/treaty/exceptions/deprecated.rb +8 -0
  34. data/lib/treaty/exceptions/execution.rb +8 -0
  35. data/lib/treaty/exceptions/method_name.rb +8 -0
  36. data/lib/treaty/exceptions/nested_attributes.rb +8 -0
  37. data/lib/treaty/exceptions/strategy.rb +8 -0
  38. data/lib/treaty/exceptions/unexpected.rb +8 -0
  39. data/lib/treaty/exceptions/validation.rb +8 -0
  40. data/lib/treaty/info/builder.rb +122 -0
  41. data/lib/treaty/info/dsl.rb +26 -0
  42. data/lib/treaty/info/result.rb +13 -0
  43. data/lib/treaty/request/attribute/attribute.rb +24 -0
  44. data/lib/treaty/request/attribute/builder.rb +22 -0
  45. data/lib/treaty/request/attribute/validation/orchestrator.rb +27 -0
  46. data/lib/treaty/request/attribute/validator.rb +50 -0
  47. data/lib/treaty/request/factory.rb +32 -0
  48. data/lib/treaty/request/scope/collection.rb +21 -0
  49. data/lib/treaty/request/scope/factory.rb +42 -0
  50. data/lib/treaty/response/attribute/attribute.rb +24 -0
  51. data/lib/treaty/response/attribute/builder.rb +22 -0
  52. data/lib/treaty/response/attribute/validation/orchestrator.rb +27 -0
  53. data/lib/treaty/response/attribute/validator.rb +44 -0
  54. data/lib/treaty/response/factory.rb +38 -0
  55. data/lib/treaty/response/scope/collection.rb +21 -0
  56. data/lib/treaty/response/scope/factory.rb +42 -0
  57. data/lib/treaty/result.rb +22 -0
  58. data/lib/treaty/strategy.rb +31 -0
  59. data/lib/treaty/support/loader.rb +24 -0
  60. data/lib/treaty/version.rb +8 -1
  61. data/lib/treaty/versions/collection.rb +15 -0
  62. data/lib/treaty/versions/dsl.rb +30 -0
  63. data/lib/treaty/versions/execution/request.rb +151 -0
  64. data/lib/treaty/versions/executor.rb +14 -0
  65. data/lib/treaty/versions/factory.rb +93 -0
  66. data/lib/treaty/versions/resolver.rb +72 -0
  67. data/lib/treaty/versions/semantic.rb +22 -0
  68. data/lib/treaty/versions/workspace.rb +40 -0
  69. data/lib/treaty.rb +3 -3
  70. metadata +184 -27
  71. data/.standard.yml +0 -3
  72. data/CHANGELOG.md +0 -5
  73. data/CODE_OF_CONDUCT.md +0 -84
  74. data/LICENSE.txt +0 -21
  75. data/sig/treaty.rbs +0 -4
  76. data/treaty.gemspec +0 -35
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Treaty
4
+ module Attribute
5
+ module Option
6
+ module Validators
7
+ # Validates that attribute value matches the declared type.
8
+ #
9
+ # ## Supported Types
10
+ #
11
+ # - `:integer` - Ruby Integer
12
+ # - `:string` - Ruby String
13
+ # - `:object` - Ruby Hash (for nested objects)
14
+ # - `:array` - Ruby Array (for collections)
15
+ # - `:datetime` - Ruby DateTime, Time, or Date
16
+ #
17
+ # ## Usage Examples
18
+ #
19
+ # Simple types:
20
+ # integer :age
21
+ # string :name
22
+ # datetime :created_at
23
+ #
24
+ # Nested structures:
25
+ # object :author do
26
+ # string :name
27
+ # end
28
+ #
29
+ # array :tags do
30
+ # string :_self # Simple array of strings
31
+ # end
32
+ #
33
+ # ## Validation Rules
34
+ #
35
+ # - Validates only non-nil values (nil handling is done by RequiredValidator)
36
+ # - Type mismatch raises Treaty::Exceptions::Validation
37
+ # - Datetime accepts DateTime, Time, or Date objects
38
+ #
39
+ # ## Note
40
+ #
41
+ # TypeValidator doesn't use option_schema - it validates based on attribute_type.
42
+ # This validator is always active for all attributes.
43
+ class TypeValidator < Treaty::Attribute::Option::Base
44
+ ALLOWED_TYPES = %i[integer string object array datetime].freeze
45
+
46
+ # Validates that the attribute type is one of the allowed types
47
+ #
48
+ # @raise [Treaty::Exceptions::Validation] If type is not allowed
49
+ # @return [void]
50
+ def validate_schema!
51
+ return if ALLOWED_TYPES.include?(@attribute_type)
52
+
53
+ # TODO: It is necessary to implement a translation system (I18n).
54
+ raise Treaty::Exceptions::Validation,
55
+ "Unknown type '#{@attribute_type}' for attribute '#{@attribute_name}'. " \
56
+ "Allowed types: #{ALLOWED_TYPES.join(', ')}"
57
+ end
58
+
59
+ # Validates that the value matches the declared type
60
+ # Skips validation for nil values (handled by RequiredValidator)
61
+ #
62
+ # @param value [Object] The value to validate
63
+ # @raise [Treaty::Exceptions::Validation] If value type doesn't match
64
+ # @return [void]
65
+ def validate_value!(value) # rubocop:disable Metrics/MethodLength
66
+ return if value.nil? # Type validation doesn't check for nil, required does.
67
+
68
+ case @attribute_type
69
+ when :integer
70
+ validate_integer!(value)
71
+ when :string
72
+ validate_string!(value)
73
+ when :object
74
+ validate_object!(value)
75
+ when :array
76
+ validate_array!(value)
77
+ when :datetime
78
+ validate_datetime!(value)
79
+ end
80
+ end
81
+
82
+ private
83
+
84
+ # Validates that value is an Integer
85
+ #
86
+ # @param value [Object] The value to validate
87
+ # @raise [Treaty::Exceptions::Validation] If value is not an Integer
88
+ # @return [void]
89
+ def validate_integer!(value)
90
+ return if value.is_a?(Integer)
91
+
92
+ # TODO: It is necessary to implement a translation system (I18n).
93
+ raise Treaty::Exceptions::Validation,
94
+ "Attribute '#{@attribute_name}' must be an Integer, got #{value.class}"
95
+ end
96
+
97
+ # Validates that value is a String
98
+ #
99
+ # @param value [Object] The value to validate
100
+ # @raise [Treaty::Exceptions::Validation] If value is not a String
101
+ # @return [void]
102
+ def validate_string!(value)
103
+ return if value.is_a?(String)
104
+
105
+ # TODO: It is necessary to implement a translation system (I18n).
106
+ raise Treaty::Exceptions::Validation,
107
+ "Attribute '#{@attribute_name}' must be a String, got #{value.class}"
108
+ end
109
+
110
+ # Validates that value is a Hash (object type)
111
+ #
112
+ # @param value [Object] The value to validate
113
+ # @raise [Treaty::Exceptions::Validation] If value is not a Hash
114
+ # @return [void]
115
+ def validate_object!(value)
116
+ return if value.is_a?(Hash)
117
+
118
+ # TODO: It is necessary to implement a translation system (I18n).
119
+ raise Treaty::Exceptions::Validation,
120
+ "Attribute '#{@attribute_name}' must be a Hash (object), got #{value.class}"
121
+ end
122
+
123
+ # Validates that value is an Array
124
+ #
125
+ # @param value [Object] The value to validate
126
+ # @raise [Treaty::Exceptions::Validation] If value is not an Array
127
+ # @return [void]
128
+ def validate_array!(value)
129
+ return if value.is_a?(Array)
130
+
131
+ # TODO: It is necessary to implement a translation system (I18n).
132
+ raise Treaty::Exceptions::Validation,
133
+ "Attribute '#{@attribute_name}' must be an Array, got #{value.class}"
134
+ end
135
+
136
+ # Validates that value is a DateTime, Time, or Date
137
+ #
138
+ # @param value [Object] The value to validate
139
+ # @raise [Treaty::Exceptions::Validation] If value is not a datetime type
140
+ # @return [void]
141
+ def validate_datetime!(value)
142
+ # TODO: It is better to divide it into different methods for each class.
143
+ return if value.is_a?(DateTime) || value.is_a?(Time) || value.is_a?(Date)
144
+
145
+ # TODO: It is necessary to implement a translation system (I18n).
146
+ raise Treaty::Exceptions::Validation,
147
+ "Attribute '#{@attribute_name}' must be a DateTime/Time/Date, got #{value.class}"
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Treaty
4
+ module Attribute
5
+ # Normalizes options from simple mode to advanced mode.
6
+ #
7
+ # ## Purpose
8
+ #
9
+ # All options are stored and processed internally in advanced mode.
10
+ # This normalizer converts simple mode to advanced mode automatically.
11
+ #
12
+ # ## Modes Explained
13
+ #
14
+ # ### Simple Mode (Concise syntax)
15
+ # ```ruby
16
+ # {
17
+ # required: true,
18
+ # as: :value,
19
+ # in: %w[twitter linkedin github],
20
+ # default: 12
21
+ # }
22
+ # ```
23
+ #
24
+ # ### Advanced Mode (With messages)
25
+ # ```ruby
26
+ # {
27
+ # required: { is: true, message: nil },
28
+ # as: { is: :value, message: nil },
29
+ # inclusion: { in: %w[twitter linkedin github], message: nil },
30
+ # default: { is: 12, message: nil }
31
+ # }
32
+ # ```
33
+ #
34
+ # ## Key Mappings
35
+ #
36
+ # Some simple mode keys are renamed in advanced mode:
37
+ # - `in:` → `inclusion:` (with value key `:in`)
38
+ #
39
+ # Others keep the same name:
40
+ # - `required:` → `required:` (with value key `:is`)
41
+ # - `as:` → `as:` (with value key `:is`)
42
+ # - `default:` → `default:` (with value key `:is`)
43
+ #
44
+ # ## Value Keys
45
+ #
46
+ # Each option has a value key in advanced mode:
47
+ # - Default: `:is` (most options)
48
+ # - Special: `:in` (inclusion validator)
49
+ #
50
+ # ## Message Field
51
+ #
52
+ # The `message` field in advanced mode allows custom error messages:
53
+ # - `nil` - Use default message (most common)
54
+ # - String - Custom error message for validation failures
55
+ #
56
+ # ## Usage in DSL
57
+ #
58
+ # Users can write in either mode:
59
+ #
60
+ # Simple mode:
61
+ # string :provider, in: %w[twitter linkedin]
62
+ #
63
+ # Advanced mode:
64
+ # string :provider, inclusion: { in: %w[twitter linkedin], message: "Invalid provider" }
65
+ #
66
+ # Both are normalized to advanced mode internally.
67
+ class OptionNormalizer
68
+ # Maps simple mode option keys to their advanced mode configuration.
69
+ # Format: simple_key => { advanced_key:, value_key: }
70
+ OPTION_KEY_MAPPING = {
71
+ in: { advanced_key: :inclusion, value_key: :in },
72
+ as: { advanced_key: :as, value_key: :is },
73
+ default: { advanced_key: :default, value_key: :is }
74
+ }.freeze
75
+ private_constant :OPTION_KEY_MAPPING
76
+
77
+ # Reverse mapping: advanced_key => value_key
78
+ # Used to determine value key when option is already in advanced mode.
79
+ ADVANCED_KEY_TO_VALUE_KEY = OPTION_KEY_MAPPING.each_with_object({}) do |(_, config), result|
80
+ result[config.fetch(:advanced_key)] = config.fetch(:value_key)
81
+ end.freeze
82
+ private_constant :ADVANCED_KEY_TO_VALUE_KEY
83
+
84
+ DEFAULT_VALUE_KEY = :is
85
+ private_constant :DEFAULT_VALUE_KEY
86
+
87
+ class << self
88
+ # Normalizes all options from simple mode to advanced mode
89
+ #
90
+ # @param options [Hash] Options hash in simple or advanced mode
91
+ # @return [Hash] Normalized options in advanced mode
92
+ def normalize(options)
93
+ options.each_with_object({}) do |(key, value), result|
94
+ advanced_key, normalized_value = normalize_option(key, value)
95
+ result[advanced_key] = normalized_value
96
+ end
97
+ end
98
+
99
+ private
100
+
101
+ # Normalizes a single option to advanced mode
102
+ #
103
+ # @param key [Symbol] Option key
104
+ # @param value [Object] Option value
105
+ # @return [Array<Symbol, Hash>] Tuple of [advanced_key, normalized_value]
106
+ def normalize_option(key, value) # rubocop:disable Metrics/MethodLength
107
+ mapping = OPTION_KEY_MAPPING.fetch(key, nil)
108
+
109
+ if mapping.present?
110
+ # Special handling for mapped options (e.g., in -> inclusion).
111
+ advanced_key = mapping.fetch(:advanced_key)
112
+ value_key = mapping.fetch(:value_key)
113
+ normalized_value = normalize_value(value, value_key)
114
+ [advanced_key, normalized_value]
115
+ else
116
+ # Check if this key is already an advanced mode key.
117
+ value_key = ADVANCED_KEY_TO_VALUE_KEY.fetch(key, nil) || DEFAULT_VALUE_KEY
118
+ normalized_value = normalize_value(value, value_key)
119
+ [key, normalized_value]
120
+ end
121
+ end
122
+
123
+ # Normalizes option value to advanced mode format
124
+ #
125
+ # @param value [Object] The option value (simple or advanced mode)
126
+ # @param value_key [Symbol] The key to use for the value (:is or :in)
127
+ # @return [Hash] Normalized hash with value_key and :message
128
+ def normalize_value(value, value_key)
129
+ if advanced_mode?(value, value_key)
130
+ # Already in advanced mode, ensure it has both keys.
131
+ { value_key => value.fetch(value_key), message: value.fetch(:message, nil) }
132
+ else
133
+ # Simple mode, convert to advanced.
134
+ # TODO: It is necessary to implement a translation system (I18n).
135
+ { value_key => value, message: nil }
136
+ end
137
+ end
138
+
139
+ # Checks if value is already in advanced mode
140
+ #
141
+ # @param value [Object] The value to check
142
+ # @param value_key [Symbol] The expected value key
143
+ # @return [Boolean] True if value is a hash with the value key
144
+ def advanced_mode?(value, value_key)
145
+ value.is_a?(Hash) && value.key?(value_key)
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,186 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Treaty
4
+ module Attribute
5
+ # Orchestrates all option processors for a single attribute.
6
+ #
7
+ # ## Purpose
8
+ #
9
+ # Coordinates the execution of all option processors (validators and modifiers)
10
+ # for an attribute through three distinct processing phases.
11
+ #
12
+ # ## Responsibilities
13
+ #
14
+ # 1. **Processor Building** - Creates instances of all relevant option processors
15
+ # 2. **Schema Validation** - Validates DSL definition correctness (phase 1)
16
+ # 3. **Value Validation** - Validates runtime data values (phase 2)
17
+ # 4. **Value Transformation** - Transforms values through modifiers (phase 3)
18
+ # 5. **Name Transformation** - Provides target name if `as:` option is used
19
+ #
20
+ # ## Processing Phases
21
+ #
22
+ # ### Phase 1: Schema Validation
23
+ # Validates that the attribute definition in the DSL is correct.
24
+ # Called once during treaty definition loading.
25
+ #
26
+ # ```ruby
27
+ # orchestrator.validate_schema!
28
+ # ```
29
+ #
30
+ # ### Phase 2: Value Validation
31
+ # Validates that runtime data matches the constraints.
32
+ # Called for each request/response.
33
+ #
34
+ # ```ruby
35
+ # orchestrator.validate_value!(value)
36
+ # ```
37
+ #
38
+ # ### Phase 3: Value Transformation
39
+ # Transforms the value (applies defaults, renaming, etc.).
40
+ # Called for each request/response after validation.
41
+ #
42
+ # ```ruby
43
+ # transformed = orchestrator.transform_value(value)
44
+ # ```
45
+ #
46
+ # ## Usage
47
+ #
48
+ # Used by AttributeValidator to coordinate all option processing:
49
+ #
50
+ # orchestrator = OptionOrchestrator.new(attribute)
51
+ # orchestrator.validate_schema!
52
+ # orchestrator.validate_value!(value)
53
+ # transformed = orchestrator.transform_value(value)
54
+ # target_name = orchestrator.target_name
55
+ #
56
+ # ## Processor Building
57
+ #
58
+ # Automatically:
59
+ # - Builds processor instances for all defined options
60
+ # - Always includes TypeValidator (even if not explicitly defined)
61
+ # - Validates that all options are registered in Registry
62
+ # - Raises error for unknown options
63
+ #
64
+ # ## Architecture
65
+ #
66
+ # Works with:
67
+ # - Option::Registry - Looks up processor classes
68
+ # - Option::Base - Base class for all processors
69
+ # - AttributeValidator - Uses orchestrator to coordinate processing
70
+ class OptionOrchestrator
71
+ # Creates a new orchestrator instance
72
+ #
73
+ # @param attribute [Attribute::Base] The attribute to orchestrate options for
74
+ def initialize(attribute)
75
+ @attribute = attribute
76
+ @processors = build_processors
77
+ end
78
+
79
+ # Phase 1: Validates all option schemas
80
+ # Ensures DSL definition is correct and all options are registered
81
+ #
82
+ # @raise [Treaty::Exceptions::Validation] If unknown options found
83
+ # @return [void]
84
+ def validate_schema!
85
+ validate_known_options!
86
+
87
+ @processors.each_value(&:validate_schema!)
88
+ end
89
+
90
+ # Phase 2: Validates value against all option validators
91
+ # Validates runtime data against all defined constraints
92
+ #
93
+ # @param value [Object] The value to validate
94
+ # @raise [Treaty::Exceptions::Validation] If validation fails
95
+ # @return [void]
96
+ def validate_value!(value)
97
+ @processors.each_value do |processor|
98
+ processor.validate_value!(value)
99
+ end
100
+ end
101
+
102
+ # Phase 3: Transforms value through all option modifiers
103
+ # Applies transformations like defaults, type coercion, etc.
104
+ #
105
+ # @param value [Object] The value to transform
106
+ # @return [Object] Transformed value
107
+ def transform_value(value)
108
+ @processors.values.reduce(value) do |current_value, processor|
109
+ processor.transform_value(current_value)
110
+ end
111
+ end
112
+
113
+ # Checks if any processor transforms the attribute name
114
+ #
115
+ # @return [Boolean] True if any processor (like AsModifier) transforms names
116
+ def transforms_name?
117
+ @processors.values.any?(&:transforms_name?)
118
+ end
119
+
120
+ # Gets the target name from the processor that transforms names
121
+ # Returns original name if no transformation
122
+ #
123
+ # @return [Symbol] The target attribute name
124
+ def target_name
125
+ name_transformer = @processors.values.find(&:transforms_name?)
126
+ name_transformer ? name_transformer.target_name : @attribute.name
127
+ end
128
+
129
+ # Gets specific processor by option name
130
+ #
131
+ # @param option_name [Symbol] The option name (:required, :type, etc.)
132
+ # @return [Option::Base, nil] The processor instance or nil if not found
133
+ def processor_for(option_name)
134
+ @processors[option_name]
135
+ end
136
+
137
+ private
138
+
139
+ # Builds processor instances for all defined options
140
+ # Always includes TypeValidator even if not explicitly defined
141
+ #
142
+ # @return [Hash<Symbol, Option::Base>] Hash of option_name => processor
143
+ def build_processors # rubocop:disable Metrics/MethodLength
144
+ processors_hash = {}
145
+
146
+ @attribute.options.each do |option_name, option_schema|
147
+ processor_class = Option::Registry.processor_for(option_name)
148
+
149
+ next unless processor_class
150
+
151
+ processors_hash[option_name] = processor_class.new(
152
+ attribute_name: @attribute.name,
153
+ attribute_type: @attribute.type,
154
+ option_schema:
155
+ )
156
+ end
157
+
158
+ # Always include type validator
159
+ unless processors_hash.key?(:type)
160
+ processors_hash[:type] = Option::Validators::TypeValidator.new(
161
+ attribute_name: @attribute.name,
162
+ attribute_type: @attribute.type,
163
+ option_schema: nil
164
+ )
165
+ end
166
+
167
+ processors_hash
168
+ end
169
+
170
+ # Validates that all options are registered in the Registry
171
+ #
172
+ # @raise [Treaty::Exceptions::Validation] If unknown options found
173
+ # @return [void]
174
+ def validate_known_options!
175
+ unknown_options = @attribute.options.keys - Option::Registry.all_options
176
+
177
+ return if unknown_options.empty?
178
+
179
+ # TODO: It is necessary to implement a translation system (I18n).
180
+ raise Treaty::Exceptions::Validation,
181
+ "Unknown options for attribute '#{@attribute.name}': #{unknown_options.join(', ')}. " \
182
+ "Known options: #{Option::Registry.all_options.join(', ')}"
183
+ end
184
+ end
185
+ end
186
+ end
@@ -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