treaty 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +19 -18
  3. data/Rakefile +4 -2
  4. data/lib/treaty/attribute/base.rb +172 -0
  5. data/lib/treaty/attribute/builder/base.rb +142 -0
  6. data/lib/treaty/attribute/collection.rb +65 -0
  7. data/lib/treaty/attribute/helper_mapper.rb +72 -0
  8. data/lib/treaty/attribute/option/base.rb +159 -0
  9. data/lib/treaty/attribute/option/modifiers/as_modifier.rb +87 -0
  10. data/lib/treaty/attribute/option/modifiers/default_modifier.rb +103 -0
  11. data/lib/treaty/attribute/option/registry.rb +128 -0
  12. data/lib/treaty/attribute/option/registry_initializer.rb +90 -0
  13. data/lib/treaty/attribute/option/validators/inclusion_validator.rb +80 -0
  14. data/lib/treaty/attribute/option/validators/required_validator.rb +94 -0
  15. data/lib/treaty/attribute/option/validators/type_validator.rb +153 -0
  16. data/lib/treaty/attribute/option_normalizer.rb +150 -0
  17. data/lib/treaty/attribute/option_orchestrator.rb +186 -0
  18. data/lib/treaty/attribute/validation/attribute_validator.rb +144 -0
  19. data/lib/treaty/attribute/validation/base.rb +93 -0
  20. data/lib/treaty/attribute/validation/nested_array_validator.rb +194 -0
  21. data/lib/treaty/attribute/validation/nested_object_validator.rb +103 -0
  22. data/lib/treaty/attribute/validation/nested_transformer.rb +240 -0
  23. data/lib/treaty/attribute/validation/orchestrator/base.rb +196 -0
  24. data/lib/treaty/base.rb +9 -0
  25. data/lib/treaty/configuration.rb +17 -0
  26. data/lib/treaty/context/callable.rb +24 -0
  27. data/lib/treaty/context/dsl.rb +12 -0
  28. data/lib/treaty/context/workspace.rb +28 -0
  29. data/lib/treaty/controller/dsl.rb +38 -0
  30. data/lib/treaty/engine.rb +37 -0
  31. data/lib/treaty/exceptions/base.rb +8 -0
  32. data/lib/treaty/exceptions/class_name.rb +11 -0
  33. data/lib/treaty/exceptions/deprecated.rb +8 -0
  34. data/lib/treaty/exceptions/execution.rb +8 -0
  35. data/lib/treaty/exceptions/method_name.rb +8 -0
  36. data/lib/treaty/exceptions/nested_attributes.rb +8 -0
  37. data/lib/treaty/exceptions/strategy.rb +8 -0
  38. data/lib/treaty/exceptions/unexpected.rb +8 -0
  39. data/lib/treaty/exceptions/validation.rb +8 -0
  40. data/lib/treaty/info/builder.rb +122 -0
  41. data/lib/treaty/info/dsl.rb +26 -0
  42. data/lib/treaty/info/result.rb +13 -0
  43. data/lib/treaty/request/attribute/attribute.rb +24 -0
  44. data/lib/treaty/request/attribute/builder.rb +22 -0
  45. data/lib/treaty/request/attribute/validation/orchestrator.rb +27 -0
  46. data/lib/treaty/request/attribute/validator.rb +50 -0
  47. data/lib/treaty/request/factory.rb +32 -0
  48. data/lib/treaty/request/scope/collection.rb +21 -0
  49. data/lib/treaty/request/scope/factory.rb +42 -0
  50. data/lib/treaty/response/attribute/attribute.rb +24 -0
  51. data/lib/treaty/response/attribute/builder.rb +22 -0
  52. data/lib/treaty/response/attribute/validation/orchestrator.rb +27 -0
  53. data/lib/treaty/response/attribute/validator.rb +44 -0
  54. data/lib/treaty/response/factory.rb +38 -0
  55. data/lib/treaty/response/scope/collection.rb +21 -0
  56. data/lib/treaty/response/scope/factory.rb +42 -0
  57. data/lib/treaty/result.rb +22 -0
  58. data/lib/treaty/strategy.rb +31 -0
  59. data/lib/treaty/support/loader.rb +24 -0
  60. data/lib/treaty/version.rb +8 -1
  61. data/lib/treaty/versions/collection.rb +15 -0
  62. data/lib/treaty/versions/dsl.rb +30 -0
  63. data/lib/treaty/versions/execution/request.rb +151 -0
  64. data/lib/treaty/versions/executor.rb +14 -0
  65. data/lib/treaty/versions/factory.rb +93 -0
  66. data/lib/treaty/versions/resolver.rb +72 -0
  67. data/lib/treaty/versions/semantic.rb +22 -0
  68. data/lib/treaty/versions/workspace.rb +40 -0
  69. data/lib/treaty.rb +3 -3
  70. metadata +184 -27
  71. data/.standard.yml +0 -3
  72. data/CHANGELOG.md +0 -5
  73. data/CODE_OF_CONDUCT.md +0 -84
  74. data/LICENSE.txt +0 -21
  75. data/sig/treaty.rbs +0 -4
  76. data/treaty.gemspec +0 -35
@@ -0,0 +1,196 @@
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
+ # TODO: It is necessary to implement a translation system (I18n).
97
+ raise Treaty::Exceptions::Validation,
98
+ "Subclass must implement the collection_of_scopes method"
99
+ end
100
+
101
+ # Validates all attributes in a scope (deprecated, not used)
102
+ #
103
+ # @param scope_factory [ScopeFactory] The scope to validate
104
+ # @return [void]
105
+ def validate_scope!(scope_factory)
106
+ scope_data = scope_data_for(scope_factory.name)
107
+
108
+ validators_for_scope(scope_factory).each do |attribute, validator|
109
+ value = scope_data.fetch(attribute.name, nil)
110
+ validator.validate_value!(value)
111
+ end
112
+ end
113
+
114
+ # Gets cached validators for scope or builds them
115
+ #
116
+ # @param scope_factory [ScopeFactory] The scope factory
117
+ # @return [Hash] Hash of attribute => validator
118
+ def validators_for_scope(scope_factory)
119
+ @validators_cache ||= {}
120
+ @validators_cache[scope_factory] ||= build_validators_for_scope(scope_factory)
121
+ end
122
+
123
+ # Builds validators for all attributes in a scope
124
+ #
125
+ # @param scope_factory [ScopeFactory] The scope factory
126
+ # @return [Hash] Hash of attribute => validator
127
+ def build_validators_for_scope(scope_factory)
128
+ scope_factory.collection_of_attributes.each_with_object({}) do |attribute, cache|
129
+ validator = AttributeValidator.new(attribute)
130
+ validator.validate_schema!
131
+ cache[attribute] = validator
132
+ end
133
+ end
134
+
135
+ # Extracts data for a specific scope
136
+ # Must be implemented in subclasses
137
+ #
138
+ # @param _name [Symbol] The scope name
139
+ # @raise [Treaty::Exceptions::Validation] If not implemented
140
+ # @return [Hash] Scope data
141
+ def scope_data_for(_name)
142
+ # TODO: It is necessary to implement a translation system (I18n).
143
+ raise Treaty::Exceptions::Validation,
144
+ "Subclass must implement the scope_data_for method"
145
+ end
146
+
147
+ # Validates and transforms all attributes in a scope
148
+ # Handles both nested and regular attributes
149
+ #
150
+ # @param scope_factory [ScopeFactory] The scope to process
151
+ # @return [Hash] Transformed scope data
152
+ def validate_and_transform_scope!(scope_factory) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
153
+ scope_data = scope_data_for(scope_factory.name)
154
+
155
+ return scope_data if scope_factory.collection_of_attributes.empty?
156
+
157
+ transformed_scope_data = {}
158
+
159
+ validators_for_scope(scope_factory).each do |attribute, validator|
160
+ source_name = attribute.name
161
+ value = scope_data.fetch(source_name, nil)
162
+
163
+ if attribute.nested?
164
+ transformed_value = validate_and_transform_nested(attribute, value, validator)
165
+ else
166
+ validator.validate_value!(value)
167
+ transformed_value = validator.transform_value(value)
168
+ end
169
+
170
+ target_name = validator.target_name
171
+
172
+ transformed_scope_data[target_name] = transformed_value
173
+ end
174
+
175
+ transformed_scope_data
176
+ end
177
+
178
+ # Validates and transforms nested attribute (object/array)
179
+ # Delegates transformation to NestedTransformer
180
+ #
181
+ # @param attribute [Attribute::Base] The nested attribute
182
+ # @param value [Object] The value to validate and transform
183
+ # @param validator [AttributeValidator] The validator instance
184
+ # @return [Object] Transformed nested value
185
+ def validate_and_transform_nested(attribute, value, validator)
186
+ validator.validate_type!(value) unless value.nil?
187
+ validator.validate_required!(value)
188
+
189
+ transformer = NestedTransformer.new(attribute)
190
+ transformer.transform(value)
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Treaty
4
+ class Base
5
+ include Info::DSL
6
+ include Context::DSL
7
+ include Versions::DSL
8
+ end
9
+ end
@@ -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,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Treaty
4
+ module Context
5
+ module DSL
6
+ def self.included(base)
7
+ base.extend(Callable)
8
+ base.include(Workspace)
9
+ end
10
+ end
11
+ end
12
+ 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
+ # TODO: It is necessary to implement a translation system (I18n).
28
+ raise Treaty::Exceptions::ClassName, 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,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Treaty
4
+ module Exceptions
5
+ class Base < StandardError
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Treaty
4
+ module Exceptions
5
+ class ClassName < Base
6
+ def initialize(class_name)
7
+ super("Invalid class name: #{class_name}")
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Treaty
4
+ module Exceptions
5
+ class Deprecated < Base
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Treaty
4
+ module Exceptions
5
+ class Execution < Base
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Treaty
4
+ module Exceptions
5
+ class MethodName < Base
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Treaty
4
+ module Exceptions
5
+ class NestedAttributes < Base
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Treaty
4
+ module Exceptions
5
+ class Strategy < Base
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Treaty
4
+ module Exceptions
5
+ class Unexpected < Base
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Treaty
4
+ module Exceptions
5
+ class Validation < Base
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Treaty
4
+ module Info
5
+ class Builder
6
+ attr_reader :versions
7
+
8
+ def self.build(...)
9
+ new.build(...)
10
+ end
11
+
12
+ def build(collection_of_versions:)
13
+ build_all(
14
+ versions: collection_of_versions
15
+ )
16
+
17
+ self
18
+ end
19
+
20
+ private
21
+
22
+ def build_all(versions:)
23
+ build_versions_with(
24
+ collection: versions
25
+ )
26
+ end
27
+
28
+ ##########################################################################
29
+
30
+ def build_versions_with(collection:) # rubocop:disable Metrics/MethodLength
31
+ @versions = collection.map do |version|
32
+ gem_version = version.version.version
33
+ {
34
+ version: gem_version.version,
35
+ segments: gem_version.segments,
36
+ default: version.default_result,
37
+ summary: version.summary_text,
38
+ strategy: version.strategy_instance.code,
39
+ deprecated: version.deprecated_result,
40
+ executor: build_executor_with(version),
41
+ request: build_request_with(version),
42
+ response: build_response_with(version)
43
+ }
44
+ end
45
+ end
46
+
47
+ ##########################################################################
48
+
49
+ def build_executor_with(version)
50
+ {
51
+ executor: version.executor.executor,
52
+ method: version.executor.method
53
+ }
54
+ end
55
+
56
+ ##########################################################################
57
+
58
+ def build_request_with(version)
59
+ {
60
+ scopes: build_scopes_with(version.request_factory)
61
+ }
62
+ end
63
+
64
+ def build_response_with(version)
65
+ response_factory = version.response_factory
66
+ {
67
+ status: response_factory.status,
68
+ scopes: build_scopes_with(response_factory)
69
+ }
70
+ end
71
+
72
+ ##########################################################################
73
+
74
+ def build_scopes_with(request_factory)
75
+ request_factory.collection_of_scopes.to_h do |scope|
76
+ [
77
+ scope.name,
78
+ build_attributes_with(scope.collection_of_attributes)
79
+ ]
80
+ end
81
+ end
82
+
83
+ ##########################################################################
84
+
85
+ def build_attributes_with(collection, current_level = 0)
86
+ # validate_nesting_level!(current_level)
87
+
88
+ {
89
+ attributes: build_attributes_hash(collection, current_level)
90
+ }
91
+ end
92
+
93
+ def build_attributes_hash(collection, current_level)
94
+ collection.to_h do |attribute|
95
+ [
96
+ attribute.name,
97
+ {
98
+ type: attribute.type,
99
+ options: attribute.options,
100
+ attributes: build_nested_attributes(attribute, current_level)
101
+ }
102
+ ]
103
+ end
104
+ end
105
+
106
+ def build_nested_attributes(attribute, current_level)
107
+ return {} unless attribute.nested?
108
+
109
+ build_attributes_hash(attribute.collection_of_attributes, current_level + 1)
110
+ end
111
+
112
+ # def validate_nesting_level!(level)
113
+ # return unless level > Treaty::Engine.config.treaty.attribute_nesting_level
114
+ #
115
+ # # TODO: It is necessary to implement a translation system (I18n).
116
+ # raise Treaty::Exceptions::NestedAttributes,
117
+ # "Nesting level #{level} exceeds maximum allowed level of " \
118
+ # "#{Treaty::Engine.config.treaty.attribute_nesting_level}"
119
+ # end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Treaty
4
+ module Info
5
+ module DSL
6
+ def self.included(base)
7
+ base.extend(ClassMethods)
8
+ end
9
+
10
+ module ClassMethods
11
+ def info
12
+ builder = Builder.build(
13
+ collection_of_versions:
14
+ )
15
+
16
+ Result.new(builder)
17
+ end
18
+
19
+ # API: Treaty Web
20
+ def treaty?
21
+ true
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Treaty
4
+ module Info
5
+ class Result
6
+ attr_reader :versions
7
+
8
+ def initialize(builder)
9
+ @versions = builder.versions
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Treaty
4
+ module Request
5
+ module Attribute
6
+ class Attribute < Treaty::Attribute::Base
7
+ private
8
+
9
+ def apply_defaults!
10
+ # For request: required by default (true).
11
+ # TODO: It is necessary to implement a translation system (I18n).
12
+ @options[:required] ||= { is: true, message: nil }
13
+ end
14
+
15
+ def process_nested_attributes(&block)
16
+ return unless object_or_array?
17
+
18
+ builder = Builder.new(collection_of_attributes, @nesting_level + 1)
19
+ builder.instance_eval(&block)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Treaty
4
+ module Request
5
+ module Attribute
6
+ class Builder < Treaty::Attribute::Builder::Base
7
+ private
8
+
9
+ def create_attribute(name, type, *helpers, nesting_level:, **options, &block)
10
+ Attribute.new(
11
+ name,
12
+ type,
13
+ *helpers,
14
+ nesting_level:,
15
+ **options,
16
+ &block
17
+ )
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end