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.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +106 -17
  3. data/Rakefile +4 -2
  4. data/config/locales/en.yml +96 -0
  5. data/lib/treaty/attribute/base.rb +174 -0
  6. data/lib/treaty/attribute/builder/base.rb +143 -0
  7. data/lib/treaty/attribute/collection.rb +65 -0
  8. data/lib/treaty/attribute/helper_mapper.rb +72 -0
  9. data/lib/treaty/attribute/option/base.rb +160 -0
  10. data/lib/treaty/attribute/option/modifiers/as_modifier.rb +88 -0
  11. data/lib/treaty/attribute/option/modifiers/default_modifier.rb +103 -0
  12. data/lib/treaty/attribute/option/registry.rb +128 -0
  13. data/lib/treaty/attribute/option/registry_initializer.rb +90 -0
  14. data/lib/treaty/attribute/option/validators/inclusion_validator.rb +80 -0
  15. data/lib/treaty/attribute/option/validators/required_validator.rb +92 -0
  16. data/lib/treaty/attribute/option/validators/type_validator.rb +159 -0
  17. data/lib/treaty/attribute/option_normalizer.rb +151 -0
  18. data/lib/treaty/attribute/option_orchestrator.rb +187 -0
  19. data/lib/treaty/attribute/validation/attribute_validator.rb +144 -0
  20. data/lib/treaty/attribute/validation/base.rb +92 -0
  21. data/lib/treaty/attribute/validation/nested_array_validator.rb +199 -0
  22. data/lib/treaty/attribute/validation/nested_object_validator.rb +103 -0
  23. data/lib/treaty/attribute/validation/nested_transformer.rb +246 -0
  24. data/lib/treaty/attribute/validation/orchestrator/base.rb +194 -0
  25. data/lib/treaty/base.rb +9 -0
  26. data/lib/treaty/configuration.rb +17 -0
  27. data/lib/treaty/context/callable.rb +24 -0
  28. data/lib/treaty/context/dsl.rb +12 -0
  29. data/lib/treaty/context/workspace.rb +28 -0
  30. data/lib/treaty/controller/dsl.rb +38 -0
  31. data/lib/treaty/engine.rb +37 -0
  32. data/lib/treaty/exceptions/base.rb +47 -0
  33. data/lib/treaty/exceptions/class_name.rb +50 -0
  34. data/lib/treaty/exceptions/deprecated.rb +54 -0
  35. data/lib/treaty/exceptions/execution.rb +66 -0
  36. data/lib/treaty/exceptions/method_name.rb +55 -0
  37. data/lib/treaty/exceptions/nested_attributes.rb +65 -0
  38. data/lib/treaty/exceptions/not_implemented.rb +32 -0
  39. data/lib/treaty/exceptions/strategy.rb +63 -0
  40. data/lib/treaty/exceptions/unexpected.rb +70 -0
  41. data/lib/treaty/exceptions/validation.rb +97 -0
  42. data/lib/treaty/info/builder.rb +122 -0
  43. data/lib/treaty/info/dsl.rb +26 -0
  44. data/lib/treaty/info/result.rb +13 -0
  45. data/lib/treaty/request/attribute/attribute.rb +24 -0
  46. data/lib/treaty/request/attribute/builder.rb +22 -0
  47. data/lib/treaty/request/attribute/validation/orchestrator.rb +27 -0
  48. data/lib/treaty/request/attribute/validator.rb +50 -0
  49. data/lib/treaty/request/factory.rb +32 -0
  50. data/lib/treaty/request/scope/collection.rb +21 -0
  51. data/lib/treaty/request/scope/factory.rb +42 -0
  52. data/lib/treaty/response/attribute/attribute.rb +24 -0
  53. data/lib/treaty/response/attribute/builder.rb +22 -0
  54. data/lib/treaty/response/attribute/validation/orchestrator.rb +27 -0
  55. data/lib/treaty/response/attribute/validator.rb +44 -0
  56. data/lib/treaty/response/factory.rb +38 -0
  57. data/lib/treaty/response/scope/collection.rb +21 -0
  58. data/lib/treaty/response/scope/factory.rb +42 -0
  59. data/lib/treaty/result.rb +22 -0
  60. data/lib/treaty/strategy.rb +31 -0
  61. data/lib/treaty/support/loader.rb +24 -0
  62. data/lib/treaty/version.rb +8 -1
  63. data/lib/treaty/versions/collection.rb +15 -0
  64. data/lib/treaty/versions/dsl.rb +30 -0
  65. data/lib/treaty/versions/execution/request.rb +147 -0
  66. data/lib/treaty/versions/executor.rb +14 -0
  67. data/lib/treaty/versions/factory.rb +92 -0
  68. data/lib/treaty/versions/resolver.rb +69 -0
  69. data/lib/treaty/versions/semantic.rb +22 -0
  70. data/lib/treaty/versions/workspace.rb +40 -0
  71. data/lib/treaty.rb +3 -3
  72. metadata +200 -27
  73. data/.standard.yml +0 -3
  74. data/CHANGELOG.md +0 -5
  75. data/CODE_OF_CONDUCT.md +0 -84
  76. data/LICENSE.txt +0 -21
  77. data/sig/treaty.rbs +0 -4
  78. data/treaty.gemspec +0 -35
@@ -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
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Treaty
4
+ module Attribute
5
+ module Validation
6
+ # Base class for validation strategies (adapter vs non-adapter).
7
+ #
8
+ # ## Purpose
9
+ #
10
+ # Provides common interface for validation strategies used in Treaty.
11
+ # Subclasses implement specific validation logic for different strategies.
12
+ #
13
+ # ## Responsibilities
14
+ #
15
+ # 1. **Strategy Interface** - Defines common validation interface
16
+ # 2. **Factory Pattern** - Provides class-level validate! method
17
+ # 3. **Strategy Detection** - Checks if adapter strategy is active
18
+ #
19
+ # ## Subclasses
20
+ #
21
+ # - Request::Validation - Validates request data (uses Orchestrator::Request)
22
+ # - Response::Validation - Validates response data (uses Orchestrator::Response)
23
+ #
24
+ # ## Usage
25
+ #
26
+ # Subclasses must implement:
27
+ # - `validate!` - Performs validation and returns transformed data
28
+ #
29
+ # Example usage:
30
+ # Request::Validation.validate!(version_factory: factory, data: params)
31
+ #
32
+ # ## Strategy Pattern
33
+ #
34
+ # The validation system supports two strategies:
35
+ # - **Adapter Strategy** - Adapts between different API versions
36
+ # - **Standard Strategy** - Direct version handling
37
+ #
38
+ # This base class provides `adapter_strategy?` helper to check current strategy.
39
+ #
40
+ # ## Factory Method
41
+ #
42
+ # The `self.validate!(...)` class method provides a convenient factory pattern:
43
+ # ```ruby
44
+ # Request::Validation.validate!(version_factory: factory, data: params)
45
+ # # Equivalent to:
46
+ # Request::Validation.new(version_factory: factory).validate!(data: params)
47
+ # ```
48
+ #
49
+ # ## Architecture
50
+ #
51
+ # Works with:
52
+ # - VersionFactory - Provides version and strategy information
53
+ # - Orchestrator::Base - Performs actual validation and transformation
54
+ class Base
55
+ # Class-level factory method for validation
56
+ # Creates instance and calls validate!
57
+ #
58
+ # @param args [Hash] Arguments passed to initialize and validate!
59
+ # @return [Hash] Validated and transformed data
60
+ def self.validate!(...)
61
+ new(...).validate!
62
+ end
63
+
64
+ # Creates a new validation instance
65
+ #
66
+ # @param version_factory [VersionFactory] Factory containing version and strategy
67
+ def initialize(version_factory:)
68
+ @version_factory = version_factory
69
+ end
70
+
71
+ # Performs validation and transformation
72
+ # Must be implemented in subclasses
73
+ #
74
+ # @raise [Treaty::Exceptions::NotImplemented] If subclass doesn't implement
75
+ # @return [Hash] Validated and transformed data
76
+ def validate!
77
+ raise Treaty::Exceptions::Validation,
78
+ I18n.t("treaty.attributes.validators.nested.orchestrator.collection_not_implemented")
79
+ end
80
+
81
+ private
82
+
83
+ # Checks if adapter strategy is active
84
+ #
85
+ # @return [Boolean] True if using adapter strategy
86
+ def adapter_strategy?
87
+ @version_factory.strategy_instance.adapter?
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Treaty
4
+ module Attribute
5
+ module Validation
6
+ # Validates array elements against nested attribute definitions.
7
+ #
8
+ # ## Purpose
9
+ #
10
+ # Performs validation for nested array attributes during the validation phase.
11
+ # Handles both simple arrays (with :_self scope) and complex arrays (objects).
12
+ #
13
+ # ## Responsibilities
14
+ #
15
+ # 1. **Simple Array Validation** - Validates primitive values in arrays
16
+ # 2. **Complex Array Validation** - Validates hash objects within arrays
17
+ # 3. **Error Context** - Provides clear error messages with array index
18
+ # 4. **Type Checking** - Ensures elements match expected types
19
+ #
20
+ # ## Array Types
21
+ #
22
+ # ### Simple Array (`:_self` scope)
23
+ # Array containing primitive values (strings, integers, etc.)
24
+ #
25
+ # ```ruby
26
+ # array :tags do
27
+ # string :_self # Array of strings
28
+ # end
29
+ # ```
30
+ #
31
+ # Validates: `["ruby", "rails", "api"]`
32
+ #
33
+ # ### Complex Array (regular attributes)
34
+ # Array containing hash objects with defined structure
35
+ #
36
+ # ```ruby
37
+ # array :authors do
38
+ # string :name, :required
39
+ # string :email
40
+ # end
41
+ # ```
42
+ #
43
+ # Validates: `[{ name: "Alice", email: "alice@example.com" }, ...]`
44
+ #
45
+ # ## Usage
46
+ #
47
+ # Called by AttributeValidator for nested arrays:
48
+ #
49
+ # validator = NestedArrayValidator.new(attribute)
50
+ # validator.validate!(array_value)
51
+ #
52
+ # ## Error Handling
53
+ #
54
+ # Provides contextual error messages including:
55
+ # - Array attribute name
56
+ # - Element index (0-based)
57
+ # - Specific validation error
58
+ #
59
+ # Example error:
60
+ # "Error in array 'tags' at index 2: Element must match one of the defined types"
61
+ #
62
+ # ## Architecture
63
+ #
64
+ # Uses:
65
+ # - `AttributeValidator` - Validates individual elements
66
+ # - Caches validators for performance
67
+ # - Separates self validators from regular validators
68
+ class NestedArrayValidator
69
+ # Creates a new nested array validator
70
+ #
71
+ # @param attribute [Attribute::Base] The array-type attribute with nested attributes
72
+ def initialize(attribute)
73
+ @attribute = attribute
74
+ @self_validators = nil
75
+ @regular_validators = nil
76
+ end
77
+
78
+ # Validates all items in an array
79
+ # Skips validation if value is not an Array
80
+ #
81
+ # @param array [Array] The array to validate
82
+ # @raise [Treaty::Exceptions::Validation] If any item validation fails
83
+ # @return [void]
84
+ def validate!(array)
85
+ return unless array.is_a?(Array)
86
+
87
+ array.each_with_index do |array_item, index|
88
+ validate_self_array_item!(array_item, index) if self_validators.any?
89
+
90
+ validate_regular_array_item!(array_item, index) if regular_validators.any?
91
+ end
92
+ end
93
+
94
+ private
95
+
96
+ # Validates array item for simple arrays (with :_self scope)
97
+ # Simple array contains primitive values: strings, integers, datetimes, etc.
98
+ # Example: ["ruby", "rails", "api"] where each item is a String
99
+ #
100
+ # @param array_item [String, Integer, DateTime] Primitive value from simple array
101
+ # @param index [Integer] Array index for error messages
102
+ # @raise [Treaty::Exceptions::Validation] If primitive value doesn't match defined type
103
+ # @return [void]
104
+ def validate_self_array_item!(array_item, index) # rubocop:disable Metrics/MethodLength
105
+ errors = []
106
+
107
+ validated = self_validators.any? do |validator|
108
+ validator.validate_value!(array_item)
109
+ true
110
+ rescue Treaty::Exceptions::Validation => e
111
+ errors << e.message
112
+ false
113
+ end
114
+
115
+ return if validated
116
+
117
+ raise Treaty::Exceptions::Validation,
118
+ I18n.t("treaty.attributes.validators.nested.array.element_validation_error",
119
+ attribute: @attribute.name,
120
+ index:,
121
+ errors: errors.join("; "))
122
+ end
123
+
124
+ # Validates array item for complex arrays (with regular attributes)
125
+ # Complex array contains hash objects with defined structure
126
+ # Example: [{ name: "Alice", email: "alice@example.com" }, ...] where each item is a Hash
127
+ #
128
+ # @param array_item [Hash] Hash object from complex array
129
+ # @param index [Integer] Array index for error messages
130
+ # @raise [Treaty::Exceptions::Validation] If item is not Hash or nested validation fails
131
+ # @return [void]
132
+ def validate_regular_array_item!(array_item, index) # rubocop:disable Metrics/MethodLength
133
+ unless array_item.is_a?(Hash)
134
+ raise Treaty::Exceptions::Validation,
135
+ I18n.t("treaty.attributes.validators.nested.array.element_type_error",
136
+ attribute: @attribute.name,
137
+ index:,
138
+ actual: array_item.class)
139
+ end
140
+
141
+ regular_validators.each do |nested_attribute, validator|
142
+ nested_value = array_item.fetch(nested_attribute.name, nil)
143
+ validator.validate_value!(nested_value)
144
+ rescue Treaty::Exceptions::Validation => e
145
+ raise Treaty::Exceptions::Validation,
146
+ I18n.t("treaty.attributes.validators.nested.array.attribute_error",
147
+ attribute: @attribute.name,
148
+ index:,
149
+ message: e.message)
150
+ end
151
+ end
152
+
153
+ ########################################################################
154
+
155
+ # Gets cached self validators or builds them
156
+ #
157
+ # @return [Array<AttributeValidator>] Validators for :_self attributes
158
+ def self_validators
159
+ @self_validators ||= build_self_validators
160
+ end
161
+
162
+ # Gets cached regular validators or builds them
163
+ #
164
+ # @return [Hash] Hash of nested_attribute => validator
165
+ def regular_validators
166
+ @regular_validators ||= build_regular_validators
167
+ end
168
+
169
+ ########################################################################
170
+
171
+ # Builds validators for :_self attributes (simple array elements)
172
+ #
173
+ # @return [Array<AttributeValidator>] Array of validators
174
+ def build_self_validators
175
+ @attribute.collection_of_attributes
176
+ .select { |attr| attr.name == :_self }
177
+ .map do |self_attribute|
178
+ validator = AttributeValidator.new(self_attribute)
179
+ validator.validate_schema!
180
+ validator
181
+ end
182
+ end
183
+
184
+ # Builds validators for regular attributes (complex array elements)
185
+ #
186
+ # @return [Hash] Hash of nested_attribute => validator
187
+ def build_regular_validators
188
+ @attribute.collection_of_attributes
189
+ .reject { |attr| attr.name == :_self }
190
+ .each_with_object({}) do |nested_attribute, cache|
191
+ validator = AttributeValidator.new(nested_attribute)
192
+ validator.validate_schema!
193
+ cache[nested_attribute] = validator
194
+ end
195
+ end
196
+ end
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Treaty
4
+ module Attribute
5
+ module Validation
6
+ # Validates nested object (hash) attributes against their defined structure.
7
+ #
8
+ # ## Purpose
9
+ #
10
+ # Performs validation for nested object attributes during the validation phase.
11
+ # Ensures hash values conform to the nested attribute definitions.
12
+ #
13
+ # ## Responsibilities
14
+ #
15
+ # 1. **Structure Validation** - Validates hash structure matches definition
16
+ # 2. **Attribute Validation** - Validates each nested attribute's value
17
+ # 3. **Type Safety** - Ensures value is a Hash before validation
18
+ # 4. **Validator Caching** - Builds and caches validators for performance
19
+ #
20
+ # ## Usage
21
+ #
22
+ # Used for object-type attributes with nested definitions:
23
+ #
24
+ # ```ruby
25
+ # object :author do
26
+ # string :name, :required
27
+ # string :email
28
+ # integer :age
29
+ # end
30
+ # ```
31
+ #
32
+ # Validates: `{ name: "Alice", email: "alice@example.com", age: 30 }`
33
+ #
34
+ # ## Usage in Code
35
+ #
36
+ # Called by AttributeValidator for nested objects:
37
+ #
38
+ # validator = NestedObjectValidator.new(attribute)
39
+ # validator.validate!(hash_value)
40
+ #
41
+ # ## Validation Flow
42
+ #
43
+ # 1. Check if value is a Hash
44
+ # 2. Build validators for all nested attributes (cached)
45
+ # 3. For each nested attribute:
46
+ # - Extract value from hash
47
+ # - Validate using AttributeValidator
48
+ # 4. Raise exception if any validation fails
49
+ #
50
+ # ## Architecture
51
+ #
52
+ # Uses:
53
+ # - `AttributeValidator` - Validates individual nested attributes
54
+ # - Caches validators to avoid rebuilding on each validation
55
+ class NestedObjectValidator
56
+ attr_reader :attribute
57
+
58
+ # Creates a new nested object validator
59
+ #
60
+ # @param attribute [Attribute::Base] The object-type attribute with nested attributes
61
+ def initialize(attribute)
62
+ @attribute = attribute
63
+ @validators_cache = nil
64
+ end
65
+
66
+ # Validates all nested attributes in a hash
67
+ # Skips validation if value is not a Hash
68
+ #
69
+ # @param hash [Hash] The hash to validate
70
+ # @raise [Treaty::Exceptions::Validation] If any nested validation fails
71
+ # @return [void]
72
+ def validate!(hash)
73
+ return unless hash.is_a?(Hash)
74
+
75
+ validators.each do |nested_attribute, nested_validator|
76
+ nested_value = hash.fetch(nested_attribute.name, nil)
77
+ nested_validator.validate_value!(nested_value)
78
+ end
79
+ end
80
+
81
+ private
82
+
83
+ # Gets cached validators or builds them
84
+ #
85
+ # @return [Hash] Hash of nested_attribute => validator
86
+ def validators
87
+ @validators ||= build_validators
88
+ end
89
+
90
+ # Builds validators for all nested attributes
91
+ #
92
+ # @return [Hash] Hash of nested_attribute => validator
93
+ def build_validators
94
+ attribute.collection_of_attributes.each_with_object({}) do |nested_attribute, cache|
95
+ validator = AttributeValidator.new(nested_attribute)
96
+ validator.validate_schema!
97
+ cache[nested_attribute] = validator
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end