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,91 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Treaty
4
- module Attribute
5
- module Option
6
- module Modifiers
7
- # Transforms attribute names during data processing.
8
- #
9
- # ## Usage Examples
10
- #
11
- # Simple mode:
12
- # # Request: expects "handle", outputs as "value"
13
- # string :handle, as: :value
14
- #
15
- # Advanced mode:
16
- # string :handle, as: { is: :value, message: nil }
17
- #
18
- # ## Use Cases
19
- #
20
- # 1. **Request to Service mapping**:
21
- # ```ruby
22
- # request do
23
- # string :user_id, as: :id
24
- # end
25
- # # Input: { user_id: "123" }
26
- # # Service receives: { id: "123" }
27
- # ```
28
- #
29
- # 2. **Service to Response mapping**:
30
- # ```ruby
31
- # response 200 do
32
- # string :id, as: :user_id
33
- # end
34
- # # Service returns: { id: "123" }
35
- # # Output: { user_id: "123" }
36
- # ```
37
- #
38
- # ## How It Works
39
- #
40
- # AsModifier doesn't transform values - it transforms attribute names.
41
- # The orchestrator uses `target_name` to map source name to target name.
42
- #
43
- # ## Advanced Mode
44
- #
45
- # Schema format: `{ is: :symbol, message: nil }`
46
- class AsModifier < Treaty::Attribute::Option::Base
47
- # Validates that target name is a Symbol
48
- #
49
- # @raise [Treaty::Exceptions::Validation] If target is not a Symbol
50
- # @return [void]
51
- def validate_schema!
52
- target = option_value
53
-
54
- return if target.is_a?(Symbol)
55
-
56
- raise Treaty::Exceptions::Validation,
57
- I18n.t(
58
- "treaty.attributes.modifiers.as.invalid_type",
59
- attribute: @attribute_name,
60
- type: target.class
61
- )
62
- end
63
-
64
- # Indicates that AsModifier transforms attribute names
65
- #
66
- # @return [Boolean] Always returns true
67
- def transforms_name?
68
- true
69
- end
70
-
71
- # Returns the target name for the attribute
72
- #
73
- # @return [Symbol] The target attribute name
74
- def target_name
75
- option_value
76
- end
77
-
78
- # AsModifier doesn't modify the value itself, only the name
79
- # The renaming is handled by the orchestrator using target_name
80
- #
81
- # @param value [Object] The value to transform
82
- # @param _root_data [Hash] Unused root data parameter
83
- # @return [Object] Unchanged value
84
- def transform_value(value, _root_data = {})
85
- value
86
- end
87
- end
88
- end
89
- end
90
- end
91
- end
@@ -1,283 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Treaty
4
- module Attribute
5
- module Option
6
- module Modifiers
7
- # Converts attribute values between different types automatically.
8
- #
9
- # ## Usage Examples
10
- #
11
- # Simple mode:
12
- # string :created_at, cast: :datetime
13
- # datetime :timestamp, cast: :string
14
- # integer :active, cast: :boolean
15
- #
16
- # Advanced mode with custom error message:
17
- # string :created_at, cast: {
18
- # to: :datetime,
19
- # message: "Invalid date format"
20
- # }
21
- #
22
- # ## Use Cases
23
- #
24
- # 1. **Request type conversion**:
25
- # ```ruby
26
- # request do
27
- # string :created_at, cast: :datetime
28
- # end
29
- # # Input: { created_at: "2024-01-15T10:30:00Z" }
30
- # # Service receives: { created_at: DateTime object }
31
- # ```
32
- #
33
- # 2. **Response type conversion**:
34
- # ```ruby
35
- # response 200 do
36
- # datetime :created_at, cast: :string
37
- # end
38
- # # Service returns: { created_at: DateTime object }
39
- # # Output: { created_at: "2024-01-15T10:30:00Z" }
40
- # ```
41
- #
42
- # 3. **Unix timestamp conversion**:
43
- # ```ruby
44
- # integer :timestamp, cast: :datetime
45
- # datetime :created_at, cast: :integer
46
- # ```
47
- #
48
- # ## Supported Conversions
49
- #
50
- # ### From Integer
51
- # - integer -> string: Converts to string representation
52
- # - integer -> boolean: 0 = false, non-zero = true
53
- # - integer -> date: Treats as Unix timestamp, converts to date
54
- # - integer -> time: Treats as Unix timestamp
55
- # - integer -> datetime: Treats as Unix timestamp, converts to datetime
56
- #
57
- # ### From String
58
- # - string -> integer: Parses integer from string
59
- # - string -> boolean: Parses truthy/falsy strings (true/false, yes/no, 1/0, on/off)
60
- # - string -> date: Parses date string
61
- # - string -> time: Parses time string
62
- # - string -> datetime: Parses datetime string (ISO8601, RFC3339, etc.)
63
- #
64
- # ### From Boolean
65
- # - boolean -> string: Converts to "true" or "false"
66
- # - boolean -> integer: true = 1, false = 0
67
- #
68
- # ### From Date
69
- # - date -> string: Converts to ISO8601 format
70
- # - date -> integer: Converts to Unix timestamp
71
- # - date -> time: Converts to Time at midnight
72
- # - date -> datetime: Converts to DateTime at midnight
73
- #
74
- # ### From Time
75
- # - time -> string: Converts to ISO8601 format
76
- # - time -> integer: Converts to Unix timestamp
77
- # - time -> date: Converts to Date
78
- # - time -> datetime: Converts to DateTime
79
- #
80
- # ### From DateTime
81
- # - datetime -> string: Converts to ISO8601 format
82
- # - datetime -> integer: Converts to Unix timestamp
83
- # - datetime -> date: Converts to Date
84
- # - datetime -> time: Converts to Time
85
- #
86
- # ## Important Notes
87
- #
88
- # - Cast option only works with scalar types (integer, string, boolean, date, time, datetime)
89
- # - Array and Object types are not supported for casting
90
- # - Casting to the same type is allowed (no-op)
91
- # - Nil values are not transformed (handled by RequiredValidator)
92
- # - All conversion errors are caught and re-raised as Validation errors
93
- #
94
- # ## Error Handling
95
- #
96
- # If conversion fails (e.g., invalid date string, non-numeric string to integer),
97
- # the error is caught and converted to a Treaty::Exceptions::Validation error.
98
- #
99
- # ## Advanced Mode
100
- #
101
- # Schema format: `{ to: :target_type, message: "Custom error" }`
102
- # Note: Uses `:to` key instead of the default `:is` key.
103
- class CastModifier < Treaty::Attribute::Option::Base # rubocop:disable Metrics/ClassLength
104
- # Types that support casting (scalar types only)
105
- ALLOWED_CAST_TYPES = %i[integer string boolean date time datetime].freeze
106
-
107
- # Validates that cast option is correctly configured
108
- #
109
- # @raise [Treaty::Exceptions::Validation] If cast configuration is invalid
110
- # @return [void]
111
- def validate_schema! # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
112
- # If option_schema is nil, cast is not used for this attribute
113
- return if @option_schema.nil?
114
-
115
- target_type = option_value
116
-
117
- # Validate that target type is a Symbol
118
- unless target_type.is_a?(Symbol)
119
- raise Treaty::Exceptions::Validation,
120
- I18n.t(
121
- "treaty.attributes.modifiers.cast.invalid_type",
122
- attribute: @attribute_name,
123
- type: target_type.class
124
- )
125
- end
126
-
127
- # Validate that source type supports casting
128
- unless ALLOWED_CAST_TYPES.include?(@attribute_type)
129
- raise Treaty::Exceptions::Validation,
130
- I18n.t(
131
- "treaty.attributes.modifiers.cast.source_not_supported",
132
- attribute: @attribute_name,
133
- source_type: @attribute_type,
134
- allowed: ALLOWED_CAST_TYPES.join(", ")
135
- )
136
- end
137
-
138
- # Validate that target type is allowed
139
- unless ALLOWED_CAST_TYPES.include?(target_type)
140
- raise Treaty::Exceptions::Validation,
141
- I18n.t(
142
- "treaty.attributes.modifiers.cast.target_not_supported",
143
- attribute: @attribute_name,
144
- target_type:,
145
- allowed: ALLOWED_CAST_TYPES.join(", ")
146
- )
147
- end
148
-
149
- # Validate that conversion from source to target is supported
150
- return if conversion_supported?(@attribute_type, target_type)
151
-
152
- raise Treaty::Exceptions::Validation,
153
- I18n.t(
154
- "treaty.attributes.modifiers.cast.conversion_not_supported",
155
- attribute: @attribute_name,
156
- from: @attribute_type,
157
- to: target_type
158
- )
159
- end
160
-
161
- # Applies type conversion to the value
162
- # Skips conversion for nil values (handled by RequiredValidator)
163
- #
164
- # @param value [Object] The current value
165
- # @param _root_data [Hash] Unused root data parameter
166
- # @return [Object] Converted value
167
- def transform_value(value, _root_data = {}) # rubocop:disable Metrics/MethodLength
168
- return value if value.nil? # Cast doesn't modify nil, required validator handles it.
169
-
170
- target_type = option_value
171
- conversion_lambda = conversion_matrix.dig(@attribute_type, target_type)
172
-
173
- # Call conversion lambda
174
- conversion_lambda.call(value:)
175
- rescue StandardError => e
176
- attributes = {
177
- attribute: @attribute_name,
178
- from: @attribute_type,
179
- to: target_type,
180
- value:,
181
- error: e.message
182
- }
183
-
184
- # Catch all exceptions from conversion execution
185
- error_message = resolve_custom_message(**attributes) || I18n.t(
186
- "treaty.attributes.modifiers.cast.conversion_error",
187
- **attributes
188
- )
189
-
190
- raise Treaty::Exceptions::Validation, error_message
191
- end
192
-
193
- protected
194
-
195
- # Override value_key to use :to instead of :is
196
- # This makes advanced mode syntax: cast: { to: :datetime }
197
- #
198
- # @return [Symbol] The key :to
199
- def value_key
200
- :to
201
- end
202
-
203
- private
204
-
205
- # Checks if conversion from source type to target type is supported
206
- #
207
- # @param from_type [Symbol] Source type
208
- # @param to_type [Symbol] Target type
209
- # @return [Boolean] True if conversion is supported
210
- def conversion_supported?(from_type, to_type)
211
- conversion_matrix.dig(from_type, to_type).present?
212
- end
213
-
214
- # Matrix of all supported type conversions
215
- # Maps from_type => to_type => conversion_lambda
216
- #
217
- # @return [Hash] Conversion matrix
218
- def conversion_matrix # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
219
- @conversion_matrix ||= {
220
- integer: {
221
- integer: ->(value:) { value }, # No-op for same type
222
- string: ->(value:) { value.to_s },
223
- boolean: ->(value:) { value != 0 },
224
- date: ->(value:) { Time.at(value).to_date },
225
- time: ->(value:) { Time.at(value) },
226
- datetime: ->(value:) { Time.at(value).to_datetime }
227
- },
228
- string: {
229
- string: ->(value:) { value }, # No-op for same type
230
- integer: ->(value:) { Integer(value) },
231
- boolean: ->(value:) { parse_boolean(value) },
232
- date: ->(value:) { Date.parse(value) },
233
- time: ->(value:) { Time.parse(value) },
234
- datetime: ->(value:) { DateTime.parse(value) }
235
- },
236
- boolean: {
237
- boolean: ->(value:) { value }, # No-op for same type
238
- string: ->(value:) { value.to_s },
239
- integer: ->(value:) { value ? 1 : 0 }
240
- },
241
- date: {
242
- date: ->(value:) { value }, # No-op for same type
243
- string: ->(value:) { value.iso8601 },
244
- integer: ->(value:) { value.to_time.to_i },
245
- time: ->(value:) { value.to_time },
246
- datetime: ->(value:) { value.to_datetime }
247
- },
248
- time: {
249
- time: ->(value:) { value }, # No-op for same type
250
- string: ->(value:) { value.iso8601 },
251
- integer: ->(value:) { value.to_i },
252
- date: ->(value:) { value.to_date },
253
- datetime: ->(value:) { value.to_datetime }
254
- },
255
- datetime: {
256
- datetime: ->(value:) { value }, # No-op for same type
257
- string: ->(value:) { value.iso8601 },
258
- integer: ->(value:) { value.to_i },
259
- date: ->(value:) { value.to_date },
260
- time: ->(value:) { value.to_time }
261
- }
262
- }
263
- end
264
-
265
- # Parses a string value into a boolean
266
- # Recognizes: true/false, yes/no, 1/0, on/off (case-insensitive)
267
- #
268
- # @param value [String] The string value to parse
269
- # @return [Boolean] Parsed boolean value
270
- # @raise [ArgumentError] If string is not a recognized boolean value
271
- def parse_boolean(value)
272
- normalized = value.to_s.downcase.strip
273
-
274
- return true if %w[true 1 yes on].include?(normalized)
275
- return false if %w[false 0 no off].include?(normalized)
276
-
277
- raise ArgumentError, "Cannot convert '#{value}' to boolean"
278
- end
279
- end
280
- end
281
- end
282
- end
283
- end
@@ -1,126 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Treaty
4
- module Attribute
5
- module Option
6
- module Modifiers
7
- # Computes attribute values from all available raw data.
8
- #
9
- # ## Key Difference from Transform
10
- #
11
- # - `transform:` receives only `value:` (the current attribute's value)
12
- # - `computed:` receives `**attributes` (ALL raw data from root level)
13
- #
14
- # ## Usage Examples
15
- #
16
- # Simple mode:
17
- # string :full_name, computed: (lambda do |**attributes|
18
- # "#{attributes.dig(:user, :first_name)} #{attributes.dig(:user, :last_name)}"
19
- # end)
20
- #
21
- # Advanced mode with custom error message:
22
- # string :full_name, computed: {
23
- # is: ->(**attributes) { "#{attributes.dig(:user, :first_name)} #{attributes.dig(:user, :last_name)}" },
24
- # message: "Failed to compute full name"
25
- # }
26
- #
27
- # ## Use Cases
28
- #
29
- # 1. **Derived fields (full name from parts)**:
30
- # ```ruby
31
- # response 200 do
32
- # object :user do
33
- # string :first_name
34
- # string :last_name
35
- # string :full_name, computed: (lambda do |**attributes|
36
- # "#{attributes.dig(:user, :first_name)} #{attributes.dig(:user, :last_name)}"
37
- # end)
38
- # end
39
- # end
40
- # ```
41
- #
42
- # 2. **Calculated values (word count)**:
43
- # ```ruby
44
- # response 200 do
45
- # object :post do
46
- # string :content
47
- # integer :word_count, computed: (lambda do |**attributes|
48
- # attributes.dig(:post, :content).to_s.split.size
49
- # end)
50
- # end
51
- # end
52
- # ```
53
- #
54
- # 3. **Cross-object computations**:
55
- # ```ruby
56
- # response 200 do
57
- # object :order do
58
- # integer :quantity
59
- # integer :unit_price
60
- # integer :total, computed: (lambda do |**attributes|
61
- # attributes.dig(:order, :quantity).to_i * attributes.dig(:order, :unit_price).to_i
62
- # end)
63
- # end
64
- # end
65
- # ```
66
- #
67
- # ## Important Notes
68
- #
69
- # - Lambda must accept `**attributes` (named argument splat)
70
- # - Receives full raw data from root level (not just current object)
71
- # - **Always computes** - ignores any existing value, result replaces everything
72
- # - All exceptions raised in lambda are caught and re-raised as Validation errors
73
- # - Computation is applied during Phase 3 (transformation phase)
74
- # - Executes FIRST in modifier chain: computed -> transform -> cast -> default -> as
75
- #
76
- # ## Advanced Mode
77
- #
78
- # Schema format: `{ is: lambda, message: nil }`
79
- class ComputedModifier < Treaty::Attribute::Option::Base
80
- # Validates that computed value is a lambda
81
- #
82
- # @raise [Treaty::Exceptions::Validation] If computed is not a Proc/lambda
83
- # @return [void]
84
- def validate_schema!
85
- computed_lambda = option_value
86
-
87
- return if computed_lambda.respond_to?(:call)
88
-
89
- raise Treaty::Exceptions::Validation,
90
- I18n.t(
91
- "treaty.attributes.modifiers.computed.invalid_type",
92
- attribute: @attribute_name,
93
- type: computed_lambda.class
94
- )
95
- end
96
-
97
- # Computes value using the provided lambda and full root data
98
- # Always executes - ignores any existing value
99
- #
100
- # @param _value [Object] The current value (ignored - always computes)
101
- # @param root_data [Hash] Full raw data from root level
102
- # @return [Object] Computed value
103
- def transform_value(_value, root_data = {}) # rubocop:disable Metrics/MethodLength
104
- computed_lambda = option_value
105
-
106
- # Call lambda with full root data as named arguments
107
- computed_lambda.call(**root_data)
108
- rescue StandardError => e
109
- attributes = {
110
- attribute: @attribute_name,
111
- error: e.message
112
- }
113
-
114
- # Catch all exceptions from lambda execution
115
- error_message = resolve_custom_message(**attributes) || I18n.t(
116
- "treaty.attributes.modifiers.computed.execution_error",
117
- **attributes
118
- )
119
-
120
- raise Treaty::Exceptions::Validation, error_message
121
- end
122
- end
123
- end
124
- end
125
- end
126
- end
@@ -1,103 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Treaty
4
- module Attribute
5
- module Option
6
- module Modifiers
7
- # Sets default values for attributes when value is nil.
8
- #
9
- # ## Usage Examples
10
- #
11
- # Simple mode with static value:
12
- # integer :limit, default: 12
13
- # string :status, default: "pending"
14
- # boolean :active, default: false
15
- #
16
- # Simple mode with dynamic value (Proc):
17
- # datetime :created_at, default: -> { Time.current }
18
- # string :uuid, default: -> { SecureRandom.uuid }
19
- #
20
- # Advanced mode:
21
- # integer :limit, default: { is: 12, message: nil }
22
- #
23
- # ## Use Cases
24
- #
25
- # 1. **Response defaults** (most common):
26
- # ```ruby
27
- # response 200 do
28
- # object :meta do
29
- # integer :limit, default: 12
30
- # integer :page, default: 1
31
- # end
32
- # end
33
- # # Service returns: { meta: { page: 1 } }
34
- # # Output: { meta: { page: 1, limit: 12 } }
35
- # ```
36
- #
37
- # 2. **Request defaults**:
38
- # ```ruby
39
- # request do
40
- # string :format, default: "json"
41
- # end
42
- # # Input: {}
43
- # # Service receives: { format: "json" }
44
- # ```
45
- #
46
- # ## Important Notes
47
- #
48
- # - Default is applied ONLY when value is nil
49
- # - Empty strings, empty arrays, false are NOT replaced
50
- # - Proc defaults are called at transformation time
51
- # - Procs receive no arguments
52
- #
53
- # ## Array and Object Types
54
- #
55
- # NOTE: DO NOT use `default: []` or `default: {}` for array/object types!
56
- # Array and object types automatically represent empty collections.
57
- #
58
- # Incorrect:
59
- # array :tags, default: [] # Wrong! Redundant
60
- # object :meta, default: {} # Wrong! Redundant
61
- #
62
- # Correct:
63
- # array :tags # Automatically handles empty array
64
- # object :meta # Automatically handles empty object
65
- #
66
- # ## Advanced Mode
67
- #
68
- # Schema format: `{ is: value_or_proc, message: nil }`
69
- class DefaultModifier < Treaty::Attribute::Option::Base
70
- # Validates schema (no validation needed)
71
- # Default value can be any type
72
- #
73
- # @return [void]
74
- def validate_schema!
75
- # Schema structure is already normalized by OptionNormalizer.
76
- # Default value can be any type, so nothing specific to validate here.
77
- end
78
-
79
- # Applies default value if current value is nil
80
- # Empty strings, empty arrays, and false are NOT replaced
81
- #
82
- # @param value [Object] The current value
83
- # @param _root_data [Hash] Unused root data parameter
84
- # @return [Object] Default value if original is nil, otherwise original value
85
- def transform_value(value, _root_data = {})
86
- # Only apply default if value is nil
87
- # Empty strings, empty arrays, false are NOT replaced
88
- return value unless value.nil?
89
-
90
- default_value = option_value
91
-
92
- # If default value is a Proc, call it to get the value
93
- if default_value.is_a?(Proc)
94
- default_value.call
95
- else
96
- default_value
97
- end
98
- end
99
- end
100
- end
101
- end
102
- end
103
- end