treaty 0.18.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 +1 -1
  3. data/config/locales/en.yml +3 -3
  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 -79
  40. data/lib/treaty/request/attribute/attribute.rb +1 -1
  41. data/lib/treaty/request/attribute/builder.rb +2 -2
  42. data/lib/treaty/request/entity.rb +1 -1
  43. data/lib/treaty/request/factory.rb +5 -5
  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 +2 -2
  47. data/lib/treaty/response/entity.rb +1 -1
  48. data/lib/treaty/response/factory.rb +5 -5
  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 -273
  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 -46
  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
@@ -1,98 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Treaty
4
- module Attribute
5
- module Option
6
- module Validators
7
- # Validates that attribute value is present (not nil and not empty).
8
- #
9
- # ## Usage Examples
10
- #
11
- # Helper mode:
12
- # string :title, :required # Maps to required: true
13
- # string :bio, :optional # Maps to required: false
14
- #
15
- # Simple mode:
16
- # string :title, required: true
17
- # string :bio, required: false
18
- #
19
- # Advanced mode:
20
- # string :title, required: { is: true, message: "Title is mandatory" }
21
- #
22
- # ## Default Behavior
23
- #
24
- # - Request attributes: required by default (required: true)
25
- # - Response attributes: optional by default (required: false)
26
- #
27
- # ## Validation Rules
28
- #
29
- # A value is considered present if:
30
- # - It is not nil
31
- # - It is not empty (for String, Array, Hash)
32
- #
33
- # ## Advanced Mode
34
- #
35
- # Schema format: `{ is: true/false, message: nil }`
36
- class RequiredValidator < Treaty::Attribute::Option::Base
37
- # Validates schema (no validation needed, already normalized)
38
- #
39
- # @return [void]
40
- def validate_schema!
41
- # Schema structure is already normalized by OptionNormalizer.
42
- # Nothing to validate here.
43
- end
44
-
45
- # Validates that required attribute has a present value
46
- #
47
- # @param value [Object] The value to validate
48
- # @raise [Treaty::Exceptions::Validation] If required but value is missing/empty
49
- # @return [void]
50
- def validate_value!(value)
51
- return unless required?
52
- return if present?(value)
53
-
54
- message = resolve_custom_message(
55
- attribute: @attribute_name,
56
- value:
57
- ) || default_message
58
-
59
- raise Treaty::Exceptions::Validation, message
60
- end
61
-
62
- private
63
-
64
- # Checks if attribute is required
65
- #
66
- # @return [Boolean] True if attribute is required
67
- def required?
68
- return false if @option_schema.nil?
69
-
70
- # Use option_value helper which correctly extracts value based on mode
71
- option_value == true
72
- end
73
-
74
- # Checks if value is present (not nil and not empty)
75
- #
76
- # @param value [Object] The value to check
77
- # @return [Boolean] True if value is present
78
- def present?(value)
79
- return false if value.nil?
80
- return false if value.respond_to?(:empty?) && value.empty?
81
-
82
- true
83
- end
84
-
85
- # Generates default error message using I18n
86
- #
87
- # @return [String] Default error message
88
- def default_message
89
- I18n.t(
90
- "treaty.attributes.validators.required.blank",
91
- attribute: @attribute_name
92
- )
93
- end
94
- end
95
- end
96
- end
97
- end
98
- end
@@ -1,217 +0,0 @@
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
- # - `:boolean` - Ruby TrueClass or FalseClass
14
- # - `:object` - Ruby Hash (for nested objects)
15
- # - `:array` - Ruby Array (for collections)
16
- # - `:date` - Ruby Date
17
- # - `:time` - Ruby Time
18
- # - `:datetime` - Ruby DateTime
19
- #
20
- # ## Usage Examples
21
- #
22
- # Simple types:
23
- # integer :age
24
- # string :name
25
- # boolean :published
26
- # date :published_on
27
- # time :created_at
28
- # datetime :updated_at
29
- #
30
- # Nested structures:
31
- # object :author do
32
- # string :name
33
- # end
34
- #
35
- # array :tags do
36
- # string :_self # Simple array of strings
37
- # end
38
- #
39
- # ## Validation Rules
40
- #
41
- # - Validates only non-nil values (nil handling is done by RequiredValidator)
42
- # - Type mismatch raises Treaty::Exceptions::Validation
43
- # - Date accepts only Date objects (not DateTime or Time)
44
- # - Time accepts only Time objects (not Date or DateTime)
45
- # - DateTime accepts only DateTime objects (not Date or Time)
46
- #
47
- # ## Note
48
- #
49
- # TypeValidator doesn't use option_schema - it validates based on attribute_type.
50
- # This validator is always active for all attributes.
51
- class TypeValidator < Treaty::Attribute::Option::Base
52
- ALLOWED_TYPES = %i[integer string boolean object array date time datetime].freeze
53
-
54
- # Validates that the attribute type is one of the allowed types
55
- #
56
- # @raise [Treaty::Exceptions::Validation] If type is not allowed
57
- # @return [void]
58
- def validate_schema!
59
- return if ALLOWED_TYPES.include?(@attribute_type)
60
-
61
- raise Treaty::Exceptions::Validation,
62
- I18n.t(
63
- "treaty.attributes.validators.type.unknown_type",
64
- type: @attribute_type,
65
- attribute: @attribute_name,
66
- allowed: ALLOWED_TYPES.join(", ")
67
- )
68
- end
69
-
70
- # Validates that the value matches the declared type
71
- # Skips validation for nil values (handled by RequiredValidator)
72
- #
73
- # @param value [Object] The value to validate
74
- # @raise [Treaty::Exceptions::Validation] If value type doesn't match
75
- # @return [void]
76
- def validate_value!(value) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
77
- return if value.nil? # Type validation doesn't check for nil, required does.
78
-
79
- case @attribute_type
80
- when :integer
81
- validate_integer!(value)
82
- when :string
83
- validate_string!(value)
84
- when :boolean
85
- validate_boolean!(value)
86
- when :object
87
- validate_object!(value)
88
- when :array
89
- validate_array!(value)
90
- when :date
91
- validate_date!(value)
92
- when :time
93
- validate_time!(value)
94
- when :datetime
95
- validate_datetime!(value)
96
- end
97
- end
98
-
99
- private
100
-
101
- # Common type validation logic
102
- # Checks if value matches expected type and raises exception with appropriate message
103
- #
104
- # @param value [Object] The value to validate
105
- # @param expected_type [Symbol] The expected type symbol
106
- # @yield Block that returns true if value is valid
107
- # @raise [Treaty::Exceptions::Validation] If type validation fails
108
- # @return [void]
109
- def validate_type!(value, expected_type)
110
- return if yield(value)
111
-
112
- actual_type = value.class
113
-
114
- attributes = {
115
- attribute: @attribute_name,
116
- value:,
117
- expected_type:,
118
- actual_type:
119
- }
120
-
121
- message = resolve_custom_message(**attributes) || default_message(**attributes)
122
-
123
- raise Treaty::Exceptions::Validation, message
124
- end
125
-
126
- # Generates default error message for type mismatch using I18n
127
- #
128
- # @param attribute [Symbol] The attribute name
129
- # @param expected_type [Symbol] The expected type
130
- # @param actual_type [Class] The actual class of the value
131
- # @return [String] Default error message
132
- def default_message(attribute:, expected_type:, actual_type:, **)
133
- I18n.t(
134
- "treaty.attributes.validators.type.mismatch.#{expected_type}",
135
- attribute:,
136
- actual: actual_type
137
- )
138
- end
139
-
140
- # Validates that value is an Integer
141
- #
142
- # @param value [Object] The value to validate
143
- # @raise [Treaty::Exceptions::Validation] If value is not an Integer
144
- # @return [void]
145
- def validate_integer!(value)
146
- validate_type!(value, :integer) { |v| v.is_a?(Integer) }
147
- end
148
-
149
- # Validates that value is a String
150
- #
151
- # @param value [Object] The value to validate
152
- # @raise [Treaty::Exceptions::Validation] If value is not a String
153
- # @return [void]
154
- def validate_string!(value)
155
- validate_type!(value, :string) { |v| v.is_a?(String) }
156
- end
157
-
158
- # Validates that value is a Boolean (TrueClass or FalseClass)
159
- #
160
- # @param value [Object] The value to validate
161
- # @raise [Treaty::Exceptions::Validation] If value is not a Boolean
162
- # @return [void]
163
- def validate_boolean!(value)
164
- validate_type!(value, :boolean) { |v| v.is_a?(TrueClass) || v.is_a?(FalseClass) }
165
- end
166
-
167
- # Validates that value is a Hash (object type)
168
- #
169
- # @param value [Object] The value to validate
170
- # @raise [Treaty::Exceptions::Validation] If value is not a Hash
171
- # @return [void]
172
- def validate_object!(value)
173
- validate_type!(value, :object) { |v| v.is_a?(Hash) }
174
- end
175
-
176
- # Validates that value is an Array
177
- #
178
- # @param value [Object] The value to validate
179
- # @raise [Treaty::Exceptions::Validation] If value is not an Array
180
- # @return [void]
181
- def validate_array!(value)
182
- validate_type!(value, :array) { |v| v.is_a?(Array) }
183
- end
184
-
185
- # Validates that value is a Date (but not DateTime, since DateTime < Date)
186
- #
187
- # @param value [Object] The value to validate
188
- # @raise [Treaty::Exceptions::Validation] If value is not a Date
189
- # @return [void]
190
- def validate_date!(value)
191
- validate_type!(value, :date) { |v| v.is_a?(Date) && !v.is_a?(DateTime) }
192
- end
193
-
194
- # Validates that value is a Time or ActiveSupport::TimeWithZone
195
- #
196
- # @param value [Object] The value to validate
197
- # @raise [Treaty::Exceptions::Validation] If value is not a Time
198
- # @return [void]
199
- def validate_time!(value)
200
- validate_type!(value, :time) do |v|
201
- v.is_a?(Time) || (defined?(ActiveSupport::TimeWithZone) && v.is_a?(ActiveSupport::TimeWithZone))
202
- end
203
- end
204
-
205
- # Validates that value is a DateTime
206
- #
207
- # @param value [Object] The value to validate
208
- # @raise [Treaty::Exceptions::Validation] If value is not a DateTime
209
- # @return [void]
210
- def validate_datetime!(value)
211
- validate_type!(value, :datetime) { |v| v.is_a?(DateTime) }
212
- end
213
- end
214
- end
215
- end
216
- end
217
- end
@@ -1,166 +0,0 @@
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
- cast: { advanced_key: :cast, value_key: :to }
75
- }.freeze
76
- private_constant :OPTION_KEY_MAPPING
77
-
78
- # Reverse mapping: advanced_key => value_key
79
- # Used to determine value key when option is already in advanced mode.
80
- ADVANCED_KEY_TO_VALUE_KEY = OPTION_KEY_MAPPING.each_with_object({}) do |(_, config), result|
81
- result[config.fetch(:advanced_key)] = config.fetch(:value_key)
82
- end.freeze
83
- private_constant :ADVANCED_KEY_TO_VALUE_KEY
84
-
85
- DEFAULT_VALUE_KEY = :is
86
- private_constant :DEFAULT_VALUE_KEY
87
-
88
- class << self
89
- # Normalizes all options from simple mode to advanced mode
90
- # and sorts them by position for consistent execution order.
91
- #
92
- # @param options [Hash] Options hash in simple or advanced mode
93
- # @return [Hash] Normalized options in advanced mode, sorted by position
94
- def normalize(options)
95
- normalized = options.each_with_object({}) do |(key, value), result|
96
- advanced_key, normalized_value = normalize_option(key, value)
97
- result[advanced_key] = normalized_value
98
- end
99
-
100
- sort_by_position(normalized)
101
- end
102
-
103
- private
104
-
105
- # Sorts options by their registered position.
106
- # Options without position (like conditionals) sort first (position 0).
107
- #
108
- # @param options_hash [Hash] Normalized options hash
109
- # @return [Hash] Options sorted by position
110
- def sort_by_position(options_hash)
111
- options_hash.sort_by do |option_name, _|
112
- Option::Registry.position_for(option_name) || 0
113
- end.to_h
114
- end
115
-
116
- # Normalizes a single option to advanced mode
117
- #
118
- # @param key [Symbol] Option key
119
- # @param value [Object] Option value
120
- # @return [Array<Symbol, Hash>] Tuple of [advanced_key, normalized_value]
121
- def normalize_option(key, value) # rubocop:disable Metrics/MethodLength
122
- mapping = OPTION_KEY_MAPPING.fetch(key, nil)
123
-
124
- if mapping.present?
125
- # Special handling for mapped options (e.g., in -> inclusion).
126
- advanced_key = mapping.fetch(:advanced_key)
127
- value_key = mapping.fetch(:value_key)
128
- normalized_value = normalize_value(value, value_key)
129
- [advanced_key, normalized_value]
130
- else
131
- # Check if this key is already an advanced mode key.
132
- value_key = ADVANCED_KEY_TO_VALUE_KEY.fetch(key, nil) || DEFAULT_VALUE_KEY
133
- normalized_value = normalize_value(value, value_key)
134
- [key, normalized_value]
135
- end
136
- end
137
-
138
- # Normalizes option value to advanced mode format
139
- #
140
- # @param value [Object] The option value (simple or advanced mode)
141
- # @param value_key [Symbol] The key to use for the value (:is or :in)
142
- # @return [Hash] Normalized hash with value_key and :message
143
- def normalize_value(value, value_key)
144
- if advanced_mode?(value, value_key)
145
- # Already in advanced mode, ensure it has both keys.
146
- # message: nil means use I18n default message from validators
147
- { value_key => value.fetch(value_key), message: value.fetch(:message, nil) }
148
- else
149
- # Simple mode, convert to advanced.
150
- # message: nil means use I18n default message from validators
151
- { value_key => value, message: nil }
152
- end
153
- end
154
-
155
- # Checks if value is already in advanced mode
156
- #
157
- # @param value [Object] The value to check
158
- # @param value_key [Symbol] The expected value key
159
- # @return [Boolean] True if value is a hash with the value key
160
- def advanced_mode?(value, value_key)
161
- value.is_a?(Hash) && value.key?(value_key)
162
- end
163
- end
164
- end
165
- end
166
- end
@@ -1,190 +0,0 @@
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
- # @param root_data [Hash] Full raw data from root level (used by computed modifier)
107
- # @return [Object] Transformed value
108
- def transform_value(value, root_data = {})
109
- @processors.values.reduce(value) do |current_value, processor|
110
- processor.transform_value(current_value, root_data)
111
- end
112
- end
113
-
114
- # Checks if any processor transforms the attribute name
115
- #
116
- # @return [Boolean] True if any processor (like AsModifier) transforms names
117
- def transforms_name?
118
- @processors.values.any?(&:transforms_name?)
119
- end
120
-
121
- # Gets the target name from the processor that transforms names
122
- # Returns original name if no transformation
123
- #
124
- # @return [Symbol] The target attribute name
125
- def target_name
126
- name_transformer = @processors.values.find(&:transforms_name?)
127
- name_transformer ? name_transformer.target_name : @attribute.name
128
- end
129
-
130
- # Gets specific processor by option name
131
- #
132
- # @param option_name [Symbol] The option name (:required, :type, etc.)
133
- # @return [Option::Base, nil] The processor instance or nil if not found
134
- def processor_for(option_name)
135
- @processors.fetch(option_name)
136
- end
137
-
138
- private
139
-
140
- # Builds processor instances for all defined options
141
- # Always includes TypeValidator even if not explicitly defined
142
- #
143
- # @return [Hash<Symbol, Option::Base>] Hash of option_name => processor
144
- def build_processors # rubocop:disable Metrics/MethodLength
145
- processors_hash = {}
146
-
147
- @attribute.options.each do |option_name, option_schema|
148
- processor_class = Option::Registry.processor_for(option_name)
149
-
150
- next if processor_class.nil?
151
-
152
- processors_hash[option_name] = processor_class.new(
153
- attribute_name: @attribute.name,
154
- attribute_type: @attribute.type,
155
- option_schema:
156
- )
157
- end
158
-
159
- # Always include type validator
160
- unless processors_hash.key?(:type)
161
- processors_hash[:type] = Option::Validators::TypeValidator.new(
162
- attribute_name: @attribute.name,
163
- attribute_type: @attribute.type,
164
- option_schema: nil
165
- )
166
- end
167
-
168
- processors_hash
169
- end
170
-
171
- # Validates that all options are registered in the Registry
172
- #
173
- # @raise [Treaty::Exceptions::Validation] If unknown options found
174
- # @return [void]
175
- def validate_known_options!
176
- unknown_options = @attribute.options.keys - Option::Registry.all_options
177
-
178
- return if unknown_options.empty?
179
-
180
- raise Treaty::Exceptions::Validation,
181
- I18n.t(
182
- "treaty.attributes.options.unknown",
183
- attribute: @attribute.name,
184
- unknown: unknown_options.join(", "),
185
- known: Option::Registry.all_options.join(", ")
186
- )
187
- end
188
- end
189
- end
190
- end