treaty 0.0.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +106 -17
- data/Rakefile +4 -2
- data/config/locales/en.yml +96 -0
- data/lib/treaty/attribute/base.rb +174 -0
- data/lib/treaty/attribute/builder/base.rb +143 -0
- data/lib/treaty/attribute/collection.rb +65 -0
- data/lib/treaty/attribute/helper_mapper.rb +72 -0
- data/lib/treaty/attribute/option/base.rb +160 -0
- data/lib/treaty/attribute/option/modifiers/as_modifier.rb +88 -0
- data/lib/treaty/attribute/option/modifiers/default_modifier.rb +103 -0
- data/lib/treaty/attribute/option/registry.rb +128 -0
- data/lib/treaty/attribute/option/registry_initializer.rb +90 -0
- data/lib/treaty/attribute/option/validators/inclusion_validator.rb +80 -0
- data/lib/treaty/attribute/option/validators/required_validator.rb +92 -0
- data/lib/treaty/attribute/option/validators/type_validator.rb +159 -0
- data/lib/treaty/attribute/option_normalizer.rb +151 -0
- data/lib/treaty/attribute/option_orchestrator.rb +187 -0
- data/lib/treaty/attribute/validation/attribute_validator.rb +144 -0
- data/lib/treaty/attribute/validation/base.rb +92 -0
- data/lib/treaty/attribute/validation/nested_array_validator.rb +199 -0
- data/lib/treaty/attribute/validation/nested_object_validator.rb +103 -0
- data/lib/treaty/attribute/validation/nested_transformer.rb +246 -0
- data/lib/treaty/attribute/validation/orchestrator/base.rb +194 -0
- data/lib/treaty/base.rb +9 -0
- data/lib/treaty/configuration.rb +17 -0
- data/lib/treaty/context/callable.rb +24 -0
- data/lib/treaty/context/dsl.rb +12 -0
- data/lib/treaty/context/workspace.rb +28 -0
- data/lib/treaty/controller/dsl.rb +38 -0
- data/lib/treaty/engine.rb +37 -0
- data/lib/treaty/exceptions/base.rb +47 -0
- data/lib/treaty/exceptions/class_name.rb +50 -0
- data/lib/treaty/exceptions/deprecated.rb +54 -0
- data/lib/treaty/exceptions/execution.rb +66 -0
- data/lib/treaty/exceptions/method_name.rb +55 -0
- data/lib/treaty/exceptions/nested_attributes.rb +65 -0
- data/lib/treaty/exceptions/not_implemented.rb +32 -0
- data/lib/treaty/exceptions/strategy.rb +63 -0
- data/lib/treaty/exceptions/unexpected.rb +70 -0
- data/lib/treaty/exceptions/validation.rb +97 -0
- data/lib/treaty/info/builder.rb +122 -0
- data/lib/treaty/info/dsl.rb +26 -0
- data/lib/treaty/info/result.rb +13 -0
- data/lib/treaty/request/attribute/attribute.rb +24 -0
- data/lib/treaty/request/attribute/builder.rb +22 -0
- data/lib/treaty/request/attribute/validation/orchestrator.rb +27 -0
- data/lib/treaty/request/attribute/validator.rb +50 -0
- data/lib/treaty/request/factory.rb +32 -0
- data/lib/treaty/request/scope/collection.rb +21 -0
- data/lib/treaty/request/scope/factory.rb +42 -0
- data/lib/treaty/response/attribute/attribute.rb +24 -0
- data/lib/treaty/response/attribute/builder.rb +22 -0
- data/lib/treaty/response/attribute/validation/orchestrator.rb +27 -0
- data/lib/treaty/response/attribute/validator.rb +44 -0
- data/lib/treaty/response/factory.rb +38 -0
- data/lib/treaty/response/scope/collection.rb +21 -0
- data/lib/treaty/response/scope/factory.rb +42 -0
- data/lib/treaty/result.rb +22 -0
- data/lib/treaty/strategy.rb +31 -0
- data/lib/treaty/support/loader.rb +24 -0
- data/lib/treaty/version.rb +8 -1
- data/lib/treaty/versions/collection.rb +15 -0
- data/lib/treaty/versions/dsl.rb +30 -0
- data/lib/treaty/versions/execution/request.rb +147 -0
- data/lib/treaty/versions/executor.rb +14 -0
- data/lib/treaty/versions/factory.rb +92 -0
- data/lib/treaty/versions/resolver.rb +69 -0
- data/lib/treaty/versions/semantic.rb +22 -0
- data/lib/treaty/versions/workspace.rb +40 -0
- data/lib/treaty.rb +3 -3
- metadata +200 -27
- data/.standard.yml +0 -3
- data/CHANGELOG.md +0 -5
- data/CODE_OF_CONDUCT.md +0 -84
- data/LICENSE.txt +0 -21
- data/sig/treaty.rbs +0 -4
- data/treaty.gemspec +0 -35
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
module Attribute
|
|
5
|
+
module Validation
|
|
6
|
+
# Handles transformation of nested attributes (objects and arrays).
|
|
7
|
+
# Extracted from Orchestrator::Base to reduce complexity.
|
|
8
|
+
class NestedTransformer
|
|
9
|
+
SELF_SCOPE = :_self
|
|
10
|
+
private_constant :SELF_SCOPE
|
|
11
|
+
|
|
12
|
+
attr_reader :attribute
|
|
13
|
+
|
|
14
|
+
# Creates a new nested transformer
|
|
15
|
+
#
|
|
16
|
+
# @param attribute [Attribute::Base] The attribute with nested structure
|
|
17
|
+
def initialize(attribute)
|
|
18
|
+
@attribute = attribute
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Transforms nested attribute value (object or array)
|
|
22
|
+
# Returns original value if nil or not nested
|
|
23
|
+
#
|
|
24
|
+
# @param value [Object] The value to transform
|
|
25
|
+
# @return [Object] Transformed value
|
|
26
|
+
def transform(value)
|
|
27
|
+
return value if value.nil?
|
|
28
|
+
|
|
29
|
+
case attribute.type
|
|
30
|
+
when :object
|
|
31
|
+
transform_object(value)
|
|
32
|
+
when :array
|
|
33
|
+
transform_array(value)
|
|
34
|
+
else
|
|
35
|
+
value
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
# Transforms object (hash) value
|
|
42
|
+
#
|
|
43
|
+
# @param value [Hash] The hash to transform
|
|
44
|
+
# @return [Hash] Transformed hash
|
|
45
|
+
def transform_object(value)
|
|
46
|
+
return value unless attribute.nested?
|
|
47
|
+
|
|
48
|
+
transformer = ObjectTransformer.new(attribute)
|
|
49
|
+
transformer.transform(value)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Transforms array value
|
|
53
|
+
#
|
|
54
|
+
# @param value [Array] The array to transform
|
|
55
|
+
# @return [Array] Transformed array
|
|
56
|
+
def transform_array(value)
|
|
57
|
+
return value unless attribute.nested?
|
|
58
|
+
|
|
59
|
+
transformer = ArrayTransformer.new(attribute)
|
|
60
|
+
transformer.transform(value)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Transforms object (hash) with nested attributes
|
|
64
|
+
class ObjectTransformer
|
|
65
|
+
attr_reader :attribute
|
|
66
|
+
|
|
67
|
+
# Creates a new object transformer
|
|
68
|
+
#
|
|
69
|
+
# @param attribute [Attribute::Base] The object-type attribute
|
|
70
|
+
def initialize(attribute)
|
|
71
|
+
@attribute = attribute
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Transforms hash by processing all nested attributes
|
|
75
|
+
#
|
|
76
|
+
# @param value [Hash] The source hash
|
|
77
|
+
# @return [Hash] Transformed hash with processed attributes
|
|
78
|
+
def transform(value)
|
|
79
|
+
transformed = {}
|
|
80
|
+
|
|
81
|
+
attribute.collection_of_attributes.each do |nested_attribute|
|
|
82
|
+
process_attribute(nested_attribute, value, transformed)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
transformed
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
# Processes a single nested attribute
|
|
91
|
+
# Validates, transforms, and adds to target hash
|
|
92
|
+
#
|
|
93
|
+
# @param nested_attribute [Attribute::Base] Attribute to process
|
|
94
|
+
# @param source_hash [Hash] Source data
|
|
95
|
+
# @param target_hash [Hash] Target hash to populate
|
|
96
|
+
# @return [void]
|
|
97
|
+
def process_attribute(nested_attribute, source_hash, target_hash) # rubocop:disable Metrics/MethodLength
|
|
98
|
+
source_name = nested_attribute.name
|
|
99
|
+
nested_value = source_hash.fetch(source_name, nil)
|
|
100
|
+
|
|
101
|
+
validator = AttributeValidator.new(nested_attribute)
|
|
102
|
+
validator.validate_schema!
|
|
103
|
+
|
|
104
|
+
transformed_value = if nested_attribute.nested?
|
|
105
|
+
nested_transformer = NestedTransformer.new(nested_attribute)
|
|
106
|
+
validator.validate_type!(nested_value) unless nested_value.nil?
|
|
107
|
+
validator.validate_required!(nested_value)
|
|
108
|
+
nested_transformer.transform(nested_value)
|
|
109
|
+
else
|
|
110
|
+
validator.validate_value!(nested_value)
|
|
111
|
+
validator.transform_value(nested_value)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
target_name = validator.target_name
|
|
115
|
+
target_hash[target_name] = transformed_value
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Transforms array with nested attributes
|
|
120
|
+
class ArrayTransformer
|
|
121
|
+
SELF_SCOPE = :_self
|
|
122
|
+
private_constant :SELF_SCOPE
|
|
123
|
+
|
|
124
|
+
attr_reader :attribute
|
|
125
|
+
|
|
126
|
+
# Creates a new array transformer
|
|
127
|
+
#
|
|
128
|
+
# @param attribute [Attribute::Base] The array-type attribute
|
|
129
|
+
def initialize(attribute)
|
|
130
|
+
@attribute = attribute
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Transforms array by processing each element
|
|
134
|
+
# Handles both simple arrays (:_self) and complex arrays (objects)
|
|
135
|
+
#
|
|
136
|
+
# @param value [Array] The source array
|
|
137
|
+
# @return [Array] Transformed array
|
|
138
|
+
def transform(value)
|
|
139
|
+
value.each_with_index.map do |item, index|
|
|
140
|
+
if simple_array?
|
|
141
|
+
validate_simple_element(item, index)
|
|
142
|
+
item
|
|
143
|
+
else
|
|
144
|
+
transform_array_item(item, index)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
private
|
|
150
|
+
|
|
151
|
+
# Checks if this is a simple array (primitive values)
|
|
152
|
+
#
|
|
153
|
+
# @return [Boolean] True if array contains primitive values with :_self scope
|
|
154
|
+
def simple_array?
|
|
155
|
+
attribute.collection_of_attributes.size == 1 &&
|
|
156
|
+
attribute.collection_of_attributes.first.name == SELF_SCOPE
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Validates a simple array element (primitive value)
|
|
160
|
+
#
|
|
161
|
+
# @param item [Object] Array element to validate
|
|
162
|
+
# @param index [Integer] Element index for error messages
|
|
163
|
+
# @raise [Treaty::Exceptions::Validation] If validation fails
|
|
164
|
+
# @return [void]
|
|
165
|
+
def validate_simple_element(item, index) # rubocop:disable Metrics/MethodLength
|
|
166
|
+
self_attr = attribute.collection_of_attributes.first
|
|
167
|
+
validator = AttributeValidator.new(self_attr)
|
|
168
|
+
validator.validate_schema!
|
|
169
|
+
|
|
170
|
+
begin
|
|
171
|
+
validator.validate_value!(item)
|
|
172
|
+
rescue Treaty::Exceptions::Validation => e
|
|
173
|
+
raise Treaty::Exceptions::Validation,
|
|
174
|
+
I18n.t("treaty.attributes.validators.nested.array.element_validation_error",
|
|
175
|
+
attribute: attribute.name,
|
|
176
|
+
index:,
|
|
177
|
+
errors: e.message)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Transforms a complex array element (hash object)
|
|
182
|
+
#
|
|
183
|
+
# @param item [Hash] Array element to transform
|
|
184
|
+
# @param index [Integer] Element index for error messages
|
|
185
|
+
# @raise [Treaty::Exceptions::Validation] If item is not a Hash
|
|
186
|
+
# @return [Hash] Transformed hash
|
|
187
|
+
def transform_array_item(item, index) # rubocop:disable Metrics/MethodLength
|
|
188
|
+
unless item.is_a?(Hash)
|
|
189
|
+
raise Treaty::Exceptions::Validation,
|
|
190
|
+
I18n.t("treaty.attributes.validators.nested.array.element_type_error",
|
|
191
|
+
attribute: attribute.name,
|
|
192
|
+
index:,
|
|
193
|
+
actual: item.class)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
transformed = {}
|
|
197
|
+
|
|
198
|
+
attribute.collection_of_attributes.each do |nested_attribute|
|
|
199
|
+
process_attribute(nested_attribute, item, transformed, index)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
transformed
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Processes a single nested attribute in array element
|
|
206
|
+
# Validates, transforms, and adds to target hash
|
|
207
|
+
#
|
|
208
|
+
# @param nested_attribute [Attribute::Base] Attribute to process
|
|
209
|
+
# @param source_hash [Hash] Source data
|
|
210
|
+
# @param target_hash [Hash] Target hash to populate
|
|
211
|
+
# @param index [Integer] Array index for error messages
|
|
212
|
+
# @raise [Treaty::Exceptions::Validation] If validation fails
|
|
213
|
+
# @return [void]
|
|
214
|
+
def process_attribute(nested_attribute, source_hash, target_hash, index) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
|
215
|
+
source_name = nested_attribute.name
|
|
216
|
+
nested_value = source_hash.fetch(source_name, nil)
|
|
217
|
+
|
|
218
|
+
validator = AttributeValidator.new(nested_attribute)
|
|
219
|
+
validator.validate_schema!
|
|
220
|
+
|
|
221
|
+
begin
|
|
222
|
+
transformed_value = if nested_attribute.nested?
|
|
223
|
+
nested_transformer = NestedTransformer.new(nested_attribute)
|
|
224
|
+
validator.validate_type!(nested_value) unless nested_value.nil?
|
|
225
|
+
validator.validate_required!(nested_value)
|
|
226
|
+
nested_transformer.transform(nested_value)
|
|
227
|
+
else
|
|
228
|
+
validator.validate_value!(nested_value)
|
|
229
|
+
validator.transform_value(nested_value)
|
|
230
|
+
end
|
|
231
|
+
rescue Treaty::Exceptions::Validation => e
|
|
232
|
+
raise Treaty::Exceptions::Validation,
|
|
233
|
+
I18n.t("treaty.attributes.validators.nested.array.attribute_error",
|
|
234
|
+
attribute: attribute.name,
|
|
235
|
+
index:,
|
|
236
|
+
message: e.message)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
target_name = validator.target_name
|
|
240
|
+
target_hash[target_name] = transformed_value
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
module Attribute
|
|
5
|
+
module Validation
|
|
6
|
+
module Orchestrator
|
|
7
|
+
# Base orchestrator for validating and transforming data according to treaty definitions.
|
|
8
|
+
#
|
|
9
|
+
# ## Purpose
|
|
10
|
+
#
|
|
11
|
+
# Coordinates the validation and transformation of request/response data for a specific
|
|
12
|
+
# API version. Processes all scopes and their attributes, applying validations and
|
|
13
|
+
# transformations defined in the treaty DSL.
|
|
14
|
+
#
|
|
15
|
+
# ## Responsibilities
|
|
16
|
+
#
|
|
17
|
+
# 1. **Scope Processing** - Iterates through all defined scopes
|
|
18
|
+
# 2. **Attribute Validation** - Validates each attribute's value
|
|
19
|
+
# 3. **Data Transformation** - Transforms values (defaults, renaming)
|
|
20
|
+
# 4. **Nested Handling** - Delegates nested structures to NestedTransformer
|
|
21
|
+
# 5. **Result Assembly** - Builds final transformed data structure
|
|
22
|
+
#
|
|
23
|
+
# ## Usage
|
|
24
|
+
#
|
|
25
|
+
# Subclasses must implement:
|
|
26
|
+
# - `collection_of_scopes` - Returns scopes for this context (request/response)
|
|
27
|
+
# - `scope_data_for(name)` - Extracts data for a specific scope
|
|
28
|
+
#
|
|
29
|
+
# Example:
|
|
30
|
+
# orchestrator = Request::Orchestrator.new(version_factory: factory, data: params)
|
|
31
|
+
# validated_data = orchestrator.validate!
|
|
32
|
+
#
|
|
33
|
+
# ## Special Scopes
|
|
34
|
+
#
|
|
35
|
+
# - Normal scope: `{ scope_name: { ... } }`
|
|
36
|
+
# - Self scope (`:_self`): Attributes merged directly into parent
|
|
37
|
+
#
|
|
38
|
+
# ## Architecture
|
|
39
|
+
#
|
|
40
|
+
# Uses:
|
|
41
|
+
# - `AttributeValidator` - Validates individual attributes
|
|
42
|
+
# - `NestedTransformer` - Handles nested objects and arrays
|
|
43
|
+
#
|
|
44
|
+
# The refactored design separates concerns:
|
|
45
|
+
# - Orchestrator: High-level flow and scope iteration
|
|
46
|
+
# - Validator: Individual attribute validation
|
|
47
|
+
# - Transformer: Nested structure transformation
|
|
48
|
+
class Base
|
|
49
|
+
SELF_SCOPE = :_self
|
|
50
|
+
private_constant :SELF_SCOPE
|
|
51
|
+
|
|
52
|
+
attr_reader :version_factory, :data
|
|
53
|
+
|
|
54
|
+
# Class-level factory method for validation
|
|
55
|
+
# Creates instance and calls validate!
|
|
56
|
+
#
|
|
57
|
+
# @param args [Hash] Arguments passed to initialize
|
|
58
|
+
# @return [Hash] Validated and transformed data
|
|
59
|
+
def self.validate!(...)
|
|
60
|
+
new(...).validate!
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Creates a new orchestrator instance
|
|
64
|
+
#
|
|
65
|
+
# @param version_factory [VersionFactory] Factory containing version info
|
|
66
|
+
# @param data [Hash] Data to validate and transform (default: {})
|
|
67
|
+
def initialize(version_factory:, data: {})
|
|
68
|
+
@version_factory = version_factory
|
|
69
|
+
@data = data
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Validates and transforms all scopes
|
|
73
|
+
# Iterates through scopes, processes attributes, handles :_self scope
|
|
74
|
+
#
|
|
75
|
+
# @return [Hash] Transformed data with all scopes processed
|
|
76
|
+
def validate!
|
|
77
|
+
transformed_data = {}
|
|
78
|
+
|
|
79
|
+
collection_of_scopes.each do |scope_factory|
|
|
80
|
+
transformed_scope_data = validate_and_transform_scope!(scope_factory)
|
|
81
|
+
transformed_data[scope_factory.name] = transformed_scope_data if scope_factory.name != SELF_SCOPE
|
|
82
|
+
transformed_data.merge!(transformed_scope_data) if scope_factory.name == SELF_SCOPE
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
transformed_data
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
# Returns collection of scopes for this context
|
|
91
|
+
# Must be implemented in subclasses
|
|
92
|
+
#
|
|
93
|
+
# @raise [Treaty::Exceptions::Validation] If not implemented
|
|
94
|
+
# @return [Array<ScopeFactory>] Collection of scope factories
|
|
95
|
+
def collection_of_scopes
|
|
96
|
+
raise Treaty::Exceptions::Validation,
|
|
97
|
+
I18n.t("treaty.attributes.validators.nested.orchestrator.collection_not_implemented")
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Validates all attributes in a scope (deprecated, not used)
|
|
101
|
+
#
|
|
102
|
+
# @param scope_factory [ScopeFactory] The scope to validate
|
|
103
|
+
# @return [void]
|
|
104
|
+
def validate_scope!(scope_factory)
|
|
105
|
+
scope_data = scope_data_for(scope_factory.name)
|
|
106
|
+
|
|
107
|
+
validators_for_scope(scope_factory).each do |attribute, validator|
|
|
108
|
+
value = scope_data.fetch(attribute.name, nil)
|
|
109
|
+
validator.validate_value!(value)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Gets cached validators for scope or builds them
|
|
114
|
+
#
|
|
115
|
+
# @param scope_factory [ScopeFactory] The scope factory
|
|
116
|
+
# @return [Hash] Hash of attribute => validator
|
|
117
|
+
def validators_for_scope(scope_factory)
|
|
118
|
+
@validators_cache ||= {}
|
|
119
|
+
@validators_cache[scope_factory] ||= build_validators_for_scope(scope_factory)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Builds validators for all attributes in a scope
|
|
123
|
+
#
|
|
124
|
+
# @param scope_factory [ScopeFactory] The scope factory
|
|
125
|
+
# @return [Hash] Hash of attribute => validator
|
|
126
|
+
def build_validators_for_scope(scope_factory)
|
|
127
|
+
scope_factory.collection_of_attributes.each_with_object({}) do |attribute, cache|
|
|
128
|
+
validator = AttributeValidator.new(attribute)
|
|
129
|
+
validator.validate_schema!
|
|
130
|
+
cache[attribute] = validator
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Extracts data for a specific scope
|
|
135
|
+
# Must be implemented in subclasses
|
|
136
|
+
#
|
|
137
|
+
# @param _name [Symbol] The scope name
|
|
138
|
+
# @raise [Treaty::Exceptions::Validation] If not implemented
|
|
139
|
+
# @return [Hash] Scope data
|
|
140
|
+
def scope_data_for(_name)
|
|
141
|
+
raise Treaty::Exceptions::Validation,
|
|
142
|
+
I18n.t("treaty.attributes.validators.nested.orchestrator.scope_data_not_implemented")
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Validates and transforms all attributes in a scope
|
|
146
|
+
# Handles both nested and regular attributes
|
|
147
|
+
#
|
|
148
|
+
# @param scope_factory [ScopeFactory] The scope to process
|
|
149
|
+
# @return [Hash] Transformed scope data
|
|
150
|
+
def validate_and_transform_scope!(scope_factory) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
|
151
|
+
scope_data = scope_data_for(scope_factory.name)
|
|
152
|
+
|
|
153
|
+
return scope_data if scope_factory.collection_of_attributes.empty?
|
|
154
|
+
|
|
155
|
+
transformed_scope_data = {}
|
|
156
|
+
|
|
157
|
+
validators_for_scope(scope_factory).each do |attribute, validator|
|
|
158
|
+
source_name = attribute.name
|
|
159
|
+
value = scope_data.fetch(source_name, nil)
|
|
160
|
+
|
|
161
|
+
if attribute.nested?
|
|
162
|
+
transformed_value = validate_and_transform_nested(attribute, value, validator)
|
|
163
|
+
else
|
|
164
|
+
validator.validate_value!(value)
|
|
165
|
+
transformed_value = validator.transform_value(value)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
target_name = validator.target_name
|
|
169
|
+
|
|
170
|
+
transformed_scope_data[target_name] = transformed_value
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
transformed_scope_data
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Validates and transforms nested attribute (object/array)
|
|
177
|
+
# Delegates transformation to NestedTransformer
|
|
178
|
+
#
|
|
179
|
+
# @param attribute [Attribute::Base] The nested attribute
|
|
180
|
+
# @param value [Object] The value to validate and transform
|
|
181
|
+
# @param validator [AttributeValidator] The validator instance
|
|
182
|
+
# @return [Object] Transformed nested value
|
|
183
|
+
def validate_and_transform_nested(attribute, value, validator)
|
|
184
|
+
validator.validate_type!(value) unless value.nil?
|
|
185
|
+
validator.validate_required!(value)
|
|
186
|
+
|
|
187
|
+
transformer = NestedTransformer.new(attribute)
|
|
188
|
+
transformer.transform(value)
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
data/lib/treaty/base.rb
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
class Configuration
|
|
5
|
+
include ::ActiveModel::Validations
|
|
6
|
+
|
|
7
|
+
attr_accessor :version
|
|
8
|
+
|
|
9
|
+
attr_reader :attribute_nesting_level
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
@version = ->(context) { context }
|
|
13
|
+
|
|
14
|
+
@attribute_nesting_level = 5
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
module Context
|
|
5
|
+
module Callable
|
|
6
|
+
def call!(controller:, params:)
|
|
7
|
+
context = send(:new)
|
|
8
|
+
|
|
9
|
+
_call!(context, controller:, params:)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def _call!(context, controller:, params:)
|
|
15
|
+
context.send(
|
|
16
|
+
:_call!,
|
|
17
|
+
controller:,
|
|
18
|
+
params:,
|
|
19
|
+
collection_of_versions:
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
module Context
|
|
5
|
+
module Workspace
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
def _call!(
|
|
9
|
+
controller:,
|
|
10
|
+
params:,
|
|
11
|
+
collection_of_versions:
|
|
12
|
+
)
|
|
13
|
+
call!(
|
|
14
|
+
controller:,
|
|
15
|
+
params:,
|
|
16
|
+
collection_of_versions:
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def call!(
|
|
21
|
+
collection_of_versions:,
|
|
22
|
+
**
|
|
23
|
+
)
|
|
24
|
+
@collection_of_versions = collection_of_versions
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
module Controller
|
|
5
|
+
module DSL
|
|
6
|
+
def self.included(base)
|
|
7
|
+
base.extend(ClassMethods)
|
|
8
|
+
base.include(InstanceMethods)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
module ClassMethods
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def treaty(action_name)
|
|
15
|
+
define_method(action_name) do
|
|
16
|
+
treaty = treaty_class.call!(controller: self, params:)
|
|
17
|
+
|
|
18
|
+
render json: treaty.data, status: treaty.status
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
module InstanceMethods
|
|
24
|
+
def treaty_class
|
|
25
|
+
treaty_class_name.constantize
|
|
26
|
+
rescue NameError
|
|
27
|
+
raise Treaty::Exceptions::ClassName,
|
|
28
|
+
I18n.t("treaty.controller.treaty_class_not_found", class_name: treaty_class_name)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def treaty_class_name
|
|
32
|
+
# TODO: Need to move `Treaty` to configuration.
|
|
33
|
+
self.class.name.sub(/Controller$/, "::#{action_name.to_s.classify}Treaty")
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
class Engine < ::Rails::Engine
|
|
5
|
+
isolate_namespace Treaty
|
|
6
|
+
|
|
7
|
+
config.treaty = Treaty::Configuration.new
|
|
8
|
+
|
|
9
|
+
def self.configure
|
|
10
|
+
yield(config.treaty) if block_given?
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
initializer "treaty.register_option_processors", before: :load_config_initializers do
|
|
14
|
+
# Register all option processors (validators and modifiers)
|
|
15
|
+
require "treaty/attribute/option/registry_initializer"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
initializer "treaty.validate_configuration" do
|
|
19
|
+
config.after_initialize do
|
|
20
|
+
unless config.treaty.valid?
|
|
21
|
+
errors = config.treaty.errors.full_messages
|
|
22
|
+
raise "Invalid Treaty configuration: #{errors.join(', ')}"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
initializer "treaty.controller_methods" do
|
|
28
|
+
ActiveSupport.on_load(:action_controller_base) do
|
|
29
|
+
include Treaty::Controller::DSL
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
ActiveSupport.on_load(:action_controller_api) do
|
|
33
|
+
include Treaty::Controller::DSL
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
module Exceptions
|
|
5
|
+
# Base exception class for all Treaty-specific exceptions
|
|
6
|
+
#
|
|
7
|
+
# ## Purpose
|
|
8
|
+
#
|
|
9
|
+
# Serves as the parent class for all custom exceptions in the Treaty gem.
|
|
10
|
+
# Allows catching all Treaty-related exceptions with a single rescue clause.
|
|
11
|
+
#
|
|
12
|
+
# ## Usage
|
|
13
|
+
#
|
|
14
|
+
# All Treaty exceptions inherit from this base class:
|
|
15
|
+
#
|
|
16
|
+
# ```ruby
|
|
17
|
+
# begin
|
|
18
|
+
# Treaty::Base.call!(controller: self, params: params)
|
|
19
|
+
# rescue Treaty::Exceptions::Base => e
|
|
20
|
+
# # Catches any Treaty-specific exception
|
|
21
|
+
# handle_treaty_error(e)
|
|
22
|
+
# end
|
|
23
|
+
# ```
|
|
24
|
+
#
|
|
25
|
+
# ## Integration
|
|
26
|
+
#
|
|
27
|
+
# Can be used in application controllers for centralized error handling:
|
|
28
|
+
#
|
|
29
|
+
# ```ruby
|
|
30
|
+
# rescue_from Treaty::Exceptions::Base, with: :handle_treaty_error
|
|
31
|
+
# ```
|
|
32
|
+
#
|
|
33
|
+
# ## Subclasses
|
|
34
|
+
#
|
|
35
|
+
# - Validation - Attribute validation errors
|
|
36
|
+
# - Execution - Service execution errors
|
|
37
|
+
# - Deprecated - API version deprecation
|
|
38
|
+
# - Strategy - Invalid strategy specification
|
|
39
|
+
# - ClassName - Treaty class not found
|
|
40
|
+
# - MethodName - Unknown method in DSL
|
|
41
|
+
# - NestedAttributes - Nesting depth exceeded
|
|
42
|
+
# - NotImplemented - Abstract method not implemented
|
|
43
|
+
# - Unexpected - General unexpected errors
|
|
44
|
+
class Base < StandardError
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|