treaty 0.19.0 → 0.20.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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/treaty/action/base.rb +11 -0
  4. data/lib/treaty/action/context/callable.rb +90 -0
  5. data/lib/treaty/action/context/dsl.rb +56 -0
  6. data/lib/treaty/action/context/workspace.rb +92 -0
  7. data/lib/treaty/action/executor/inventory.rb +136 -0
  8. data/lib/treaty/{info/rest → action/info}/builder.rb +2 -2
  9. data/lib/treaty/{info/rest → action/info}/dsl.rb +2 -2
  10. data/lib/treaty/{info/rest → action/info}/result.rb +2 -2
  11. data/lib/treaty/action/inventory/collection.rb +77 -0
  12. data/lib/treaty/action/inventory/factory.rb +108 -0
  13. data/lib/treaty/action/inventory/inventory.rb +146 -0
  14. data/lib/treaty/action/request/attribute/attribute.rb +76 -0
  15. data/lib/treaty/action/request/attribute/builder.rb +98 -0
  16. data/lib/treaty/action/request/entity.rb +78 -0
  17. data/lib/treaty/action/request/factory.rb +116 -0
  18. data/lib/treaty/action/request/validator.rb +120 -0
  19. data/lib/treaty/action/response/attribute/attribute.rb +79 -0
  20. data/lib/treaty/action/response/attribute/builder.rb +96 -0
  21. data/lib/treaty/action/response/entity.rb +79 -0
  22. data/lib/treaty/action/response/factory.rb +129 -0
  23. data/lib/treaty/action/response/validator.rb +111 -0
  24. data/lib/treaty/action/result.rb +81 -0
  25. data/lib/treaty/action/versions/collection.rb +47 -0
  26. data/lib/treaty/action/versions/dsl.rb +116 -0
  27. data/lib/treaty/action/versions/execution/request.rb +287 -0
  28. data/lib/treaty/action/versions/executor.rb +61 -0
  29. data/lib/treaty/action/versions/factory.rb +253 -0
  30. data/lib/treaty/action/versions/resolver.rb +150 -0
  31. data/lib/treaty/action/versions/semantic.rb +64 -0
  32. data/lib/treaty/action/versions/workspace.rb +106 -0
  33. data/lib/treaty/action.rb +31 -0
  34. data/lib/treaty/controller/dsl.rb +1 -1
  35. data/lib/treaty/entity/attribute/base.rb +1 -1
  36. data/lib/treaty/entity/attribute/builder/base.rb +1 -1
  37. data/lib/treaty/entity/attribute/dsl.rb +1 -1
  38. data/lib/treaty/entity/base.rb +1 -1
  39. data/lib/treaty/entity/builder.rb +62 -5
  40. data/lib/treaty/version.rb +1 -1
  41. metadata +32 -31
  42. data/lib/treaty/base.rb +0 -9
  43. data/lib/treaty/context/callable.rb +0 -26
  44. data/lib/treaty/context/dsl.rb +0 -12
  45. data/lib/treaty/context/workspace.rb +0 -32
  46. data/lib/treaty/executor/inventory.rb +0 -122
  47. data/lib/treaty/inventory/collection.rb +0 -71
  48. data/lib/treaty/inventory/factory.rb +0 -91
  49. data/lib/treaty/inventory/inventory.rb +0 -92
  50. data/lib/treaty/request/attribute/attribute.rb +0 -25
  51. data/lib/treaty/request/attribute/builder.rb +0 -46
  52. data/lib/treaty/request/entity.rb +0 -33
  53. data/lib/treaty/request/factory.rb +0 -81
  54. data/lib/treaty/request/validator.rb +0 -60
  55. data/lib/treaty/response/attribute/attribute.rb +0 -25
  56. data/lib/treaty/response/attribute/builder.rb +0 -46
  57. data/lib/treaty/response/entity.rb +0 -33
  58. data/lib/treaty/response/factory.rb +0 -87
  59. data/lib/treaty/response/validator.rb +0 -53
  60. data/lib/treaty/result.rb +0 -23
  61. data/lib/treaty/versions/collection.rb +0 -15
  62. data/lib/treaty/versions/dsl.rb +0 -42
  63. data/lib/treaty/versions/execution/request.rb +0 -177
  64. data/lib/treaty/versions/executor.rb +0 -14
  65. data/lib/treaty/versions/factory.rb +0 -112
  66. data/lib/treaty/versions/resolver.rb +0 -70
  67. data/lib/treaty/versions/semantic.rb +0 -22
  68. data/lib/treaty/versions/workspace.rb +0 -43
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Treaty
4
+ module Action
5
+ module Inventory
6
+ # Individual inventory item with lazy evaluation.
7
+ #
8
+ # ## Purpose
9
+ #
10
+ # Represents a single piece of data that can be passed from controller
11
+ # to service. Stores the name and source, and can be evaluated against
12
+ # a controller context when needed.
13
+ #
14
+ # ## Usage
15
+ #
16
+ # Created by:
17
+ # - Inventory::Factory (when `provide` is called)
18
+ #
19
+ # Consumed by:
20
+ # - Inventory::Collection (for bulk evaluation)
21
+ # - Executor::Inventory (for lazy single-item evaluation)
22
+ #
23
+ # ## Source Types
24
+ #
25
+ # | Type | Example | Evaluation |
26
+ # |------|---------|------------|
27
+ # | Symbol | `:current_user` | Calls `context.send(:current_user)` |
28
+ # | Proc | `-> { Time.current }` | Calls `context.instance_exec(&proc)` |
29
+ # | Other | `10`, `"string"` | Returns value as-is |
30
+ #
31
+ # ## Lazy Evaluation
32
+ #
33
+ # The source is NOT evaluated at creation time. Evaluation happens
34
+ # only when `evaluate(context)` is called, typically during treaty
35
+ # execution when the service needs the value.
36
+ #
37
+ # ## Example
38
+ #
39
+ # # Symbol source (method call)
40
+ # item = Inventory.new(name: :current_user, source: :current_user)
41
+ # item.evaluate(controller) # => calls controller.current_user
42
+ #
43
+ # # Proc source (block execution)
44
+ # item = Inventory.new(name: :meta, source: -> { { time: Time.current } })
45
+ # item.evaluate(controller) # => executes block in controller context
46
+ #
47
+ # # Direct value
48
+ # item = Inventory.new(name: :limit, source: 10)
49
+ # item.evaluate(controller) # => 10
50
+ class Inventory
51
+ # @return [Symbol] Inventory item name
52
+ attr_reader :name
53
+
54
+ # @return [Symbol, Proc, Object] Source for evaluation
55
+ attr_reader :source
56
+
57
+ # Creates a new inventory item
58
+ #
59
+ # @param name [Symbol] Item name (must be non-empty Symbol)
60
+ # @param source [Symbol, Proc, Object] Evaluation source
61
+ # @raise [Treaty::Exceptions::Inventory] If name is invalid
62
+ # @raise [Treaty::Exceptions::Inventory] If source is nil
63
+ def initialize(name:, source:)
64
+ validate_name!(name)
65
+ validate_source!(source)
66
+
67
+ @name = name
68
+ @source = source
69
+ end
70
+
71
+ # Evaluates source against controller context
72
+ #
73
+ # Behavior depends on source type:
74
+ # - Symbol: calls method on context
75
+ # - Proc: executes in context scope
76
+ # - Other: returns value directly
77
+ #
78
+ # @param context [Object] Controller instance
79
+ # @return [Object] Evaluated value
80
+ # @raise [Treaty::Exceptions::Inventory] If evaluation fails
81
+ def evaluate(context) # rubocop:disable Metrics/MethodLength
82
+ case source
83
+ when Symbol
84
+ evaluate_symbol(context)
85
+ when Proc
86
+ evaluate_proc(context)
87
+ else
88
+ source
89
+ end
90
+ rescue StandardError => e
91
+ raise Treaty::Exceptions::Inventory,
92
+ I18n.t(
93
+ "treaty.inventory.evaluation_error",
94
+ name: @name,
95
+ error: e.message
96
+ )
97
+ end
98
+
99
+ private
100
+
101
+ # Evaluates Symbol source by calling method on context
102
+ #
103
+ # @param context [Object] Controller instance
104
+ # @return [Object] Method return value
105
+ def evaluate_symbol(context)
106
+ context.send(source)
107
+ end
108
+
109
+ # Evaluates Proc source in context scope
110
+ #
111
+ # Uses instance_exec so proc has access to controller
112
+ # instance variables and private methods.
113
+ #
114
+ # @param context [Object] Controller instance
115
+ # @return [Object] Proc return value
116
+ def evaluate_proc(context)
117
+ context.instance_exec(&source)
118
+ end
119
+
120
+ # Validates that name is a non-empty Symbol
121
+ #
122
+ # @param name [Object] Name to validate
123
+ # @raise [Treaty::Exceptions::Inventory] If invalid
124
+ # @return [void]
125
+ def validate_name!(name)
126
+ return if name.is_a?(Symbol) && !name.to_s.empty?
127
+
128
+ raise Treaty::Exceptions::Inventory,
129
+ I18n.t("treaty.inventory.invalid_name", name: name.inspect)
130
+ end
131
+
132
+ # Validates that source is not nil
133
+ #
134
+ # @param source [Object] Source to validate
135
+ # @raise [Treaty::Exceptions::Inventory] If nil
136
+ # @return [void]
137
+ def validate_source!(source)
138
+ return unless source.nil?
139
+
140
+ raise Treaty::Exceptions::Inventory,
141
+ I18n.t("treaty.inventory.source_required")
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Treaty
4
+ module Action
5
+ module Request
6
+ module Attribute
7
+ # Request-specific attribute definition.
8
+ #
9
+ # ## Purpose
10
+ #
11
+ # Extends Entity::Attribute::Base with Request-specific behavior.
12
+ # Key difference: attributes are **required by default**.
13
+ #
14
+ # ## Default Behavior
15
+ #
16
+ # Unlike Response attributes (optional by default), Request attributes
17
+ # default to `required: true`. This enforces strict input validation.
18
+ #
19
+ # ## Usage
20
+ #
21
+ # Created internally by:
22
+ # - Request::Attribute::Builder (when defining nested attributes)
23
+ # - Request::Entity (when defining top-level attributes)
24
+ #
25
+ # ## Nesting
26
+ #
27
+ # Object and array types create nested builders:
28
+ #
29
+ # request do
30
+ # object :post do # Creates Attribute with nested builder
31
+ # string :title # Nested attribute (required by default)
32
+ # array :tags do # Nested array
33
+ # string :_self # Array element definition
34
+ # end
35
+ # end
36
+ # end
37
+ #
38
+ # ## Example
39
+ #
40
+ # # These are equivalent:
41
+ # string :title # required by default
42
+ # string :title, :required # explicit required
43
+ #
44
+ # # Must explicitly mark optional:
45
+ # string :bio, :optional
46
+ class Attribute < Treaty::Entity::Attribute::Base
47
+ private
48
+
49
+ # Sets default required behavior for request attributes
50
+ #
51
+ # Request attributes are required by default (is: true).
52
+ # This can be overridden with `:optional` helper or `required: false`.
53
+ #
54
+ # @return [void]
55
+ def apply_defaults!
56
+ @options[:required] ||= { is: true, message: nil }
57
+ end
58
+
59
+ # Creates nested builder for object/array type processing
60
+ #
61
+ # When a block is given to object or array attributes,
62
+ # creates a Builder to process the nested attribute definitions.
63
+ #
64
+ # @param block [Proc] Block containing nested attribute definitions
65
+ # @return [void]
66
+ def process_nested_attributes(&block)
67
+ return unless object_or_array?
68
+
69
+ builder = Builder.new(collection_of_attributes, @nesting_level + 1)
70
+ builder.instance_eval(&block)
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Treaty
4
+ module Action
5
+ module Request
6
+ module Attribute
7
+ # DSL builder for defining request attributes.
8
+ #
9
+ # ## Purpose
10
+ #
11
+ # Provides Request-specific implementation of the attribute builder.
12
+ # Creates Request::Attribute::Attribute instances instead of generic ones.
13
+ #
14
+ # ## Inheritance
15
+ #
16
+ # Extends Treaty::Entity::Attribute::Builder::Base which provides:
17
+ # - DSL interface (string, integer, object, array, etc.)
18
+ # - method_missing magic for type-based method calls
19
+ # - Helper support (:required, :optional)
20
+ # - Entity reuse via use_entity
21
+ #
22
+ # ## Usage
23
+ #
24
+ # Used internally by:
25
+ # - Request::Attribute::Attribute (when processing nested object/array blocks)
26
+ #
27
+ # ## DSL Example
28
+ #
29
+ # request do
30
+ # object :post do
31
+ # string :title, :required
32
+ # string :content
33
+ # array :tags do
34
+ # string :_self
35
+ # end
36
+ # end
37
+ # end
38
+ #
39
+ # ## Methods
40
+ #
41
+ # Implements abstract methods from base class:
42
+ # - `create_attribute` - Creates Request::Attribute::Attribute
43
+ # - `deep_copy_attribute` - Deep copies attribute for use_entity support
44
+ class Builder < Treaty::Entity::Attribute::Builder::Base
45
+ private
46
+
47
+ # Creates a new request attribute instance
48
+ #
49
+ # Called by base class when defining attributes via DSL.
50
+ #
51
+ # @param name [Symbol] Attribute name
52
+ # @param type [Symbol] Attribute type (:string, :integer, :object, etc.)
53
+ # @param helpers [Array<Symbol>] Helper symbols (:required, :optional)
54
+ # @param nesting_level [Integer] Current nesting depth
55
+ # @param options [Hash] Attribute options (default:, format:, etc.)
56
+ # @param block [Proc] Block for nested attributes (object/array)
57
+ # @return [Treaty::Action::Request::Attribute::Attribute] Created attribute instance
58
+ def create_attribute(name, type, *helpers, nesting_level:, **options, &block)
59
+ Attribute.new(
60
+ name,
61
+ type,
62
+ *helpers,
63
+ nesting_level:,
64
+ **options,
65
+ &block
66
+ )
67
+ end
68
+
69
+ # Deep copies an attribute with adjusted nesting level
70
+ #
71
+ # Used when copying attributes from Entity classes via use_entity.
72
+ # Recursively copies nested attributes for object/array types.
73
+ #
74
+ # @param source_attribute [Treaty::Entity::Attribute::Base] Source attribute to copy
75
+ # @param new_nesting_level [Integer] Nesting level for copied attribute
76
+ # @return [Treaty::Action::Request::Attribute::Attribute] Deep copied attribute
77
+ def deep_copy_attribute(source_attribute, new_nesting_level) # rubocop:disable Metrics/MethodLength
78
+ copied = Attribute.new(
79
+ source_attribute.name,
80
+ source_attribute.type,
81
+ nesting_level: new_nesting_level,
82
+ **deep_copy_options(source_attribute.options)
83
+ )
84
+
85
+ return copied unless source_attribute.nested?
86
+
87
+ source_attribute.collection_of_attributes.each do |nested_source|
88
+ nested_copied = deep_copy_attribute(nested_source, new_nesting_level + 1)
89
+ copied.collection_of_attributes << nested_copied
90
+ end
91
+
92
+ copied
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Treaty
4
+ module Action
5
+ module Request
6
+ # Internal entity class for request attribute definitions.
7
+ #
8
+ # ## Purpose
9
+ #
10
+ # Provides DSL interface for defining request attributes when using
11
+ # inline block syntax in treaty definitions. Serves as the anonymous
12
+ # class base when `request do ... end` is used.
13
+ #
14
+ # ## Usage
15
+ #
16
+ # Created internally by:
17
+ # - Request::Factory (when using inline DSL blocks)
18
+ #
19
+ # ## DSL Interface
20
+ #
21
+ # Includes Treaty::Entity::Attribute::DSL which provides:
22
+ # - Type methods: string, integer, boolean, date, time, datetime
23
+ # - Structure methods: object, array
24
+ # - Helper support: :required, :optional
25
+ #
26
+ # ## Difference from Treaty::Entity::Base
27
+ #
28
+ # While Treaty::Entity::Base creates standalone entity classes,
29
+ # Request::Entity creates Request-specific attributes with:
30
+ # - Required by default behavior
31
+ # - Request::Attribute::Attribute instances
32
+ #
33
+ # ## Example
34
+ #
35
+ # # When you write:
36
+ # version 1 do
37
+ # request do
38
+ # object :post do
39
+ # string :title, :required
40
+ # end
41
+ # end
42
+ # end
43
+ #
44
+ # # Factory creates: Class.new(Request::Entity)
45
+ # # and calls string, object etc. on it
46
+ class Entity
47
+ include Treaty::Entity::Attribute::DSL
48
+
49
+ class << self
50
+ private
51
+
52
+ # Creates request-specific attribute instances
53
+ #
54
+ # Called by DSL methods (string, integer, etc.) to create
55
+ # Request::Attribute::Attribute instead of generic attributes.
56
+ #
57
+ # @param name [Symbol] Attribute name
58
+ # @param type [Symbol] Attribute type
59
+ # @param helpers [Array<Symbol>] Helper symbols (:required, :optional)
60
+ # @param nesting_level [Integer] Current nesting depth
61
+ # @param options [Hash] Attribute options
62
+ # @param block [Proc] Block for nested attributes
63
+ # @return [Treaty::Action::Request::Attribute::Attribute] Created attribute
64
+ def create_attribute(name, type, *helpers, nesting_level:, **options, &block)
65
+ Attribute::Attribute.new(
66
+ name,
67
+ type,
68
+ *helpers,
69
+ nesting_level:,
70
+ **options,
71
+ &block
72
+ )
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Treaty
4
+ module Action
5
+ module Request
6
+ # Factory for creating request attribute collections.
7
+ #
8
+ # ## Purpose
9
+ #
10
+ # Captures request attribute definitions from treaty DSL and provides
11
+ # access to the resulting attribute collection. Supports both inline
12
+ # block syntax and Entity class references.
13
+ #
14
+ # ## Usage
15
+ #
16
+ # Created internally by:
17
+ # - Versions::Factory (when `request do ... end` is called)
18
+ #
19
+ # Consumed by:
20
+ # - Request::Validator (to validate incoming params)
21
+ # - Info::Builder (to build request schema information)
22
+ #
23
+ # ## Definition Modes
24
+ #
25
+ # ### Inline Block Mode
26
+ #
27
+ # request do
28
+ # object :post do
29
+ # string :title, :required
30
+ # end
31
+ # end
32
+ #
33
+ # ### Entity Class Mode
34
+ #
35
+ # request Posts::Create::RequestEntity
36
+ #
37
+ # ## Implementation
38
+ #
39
+ # Uses method_missing to forward DSL calls to a dynamically created
40
+ # Entity class. The Entity class collects all attribute definitions.
41
+ #
42
+ # ## Example
43
+ #
44
+ # factory = Request::Factory.new
45
+ # factory.object :post do
46
+ # factory.string :title, :required
47
+ # end
48
+ # factory.collection_of_attributes # => Collection with post attribute
49
+ class Factory
50
+ # Registers an Entity class for request schema
51
+ #
52
+ # Use this to reference a pre-defined Entity class instead of
53
+ # inline attribute definitions.
54
+ #
55
+ # @param entity_class [Class] Must be Treaty::Entity::Base subclass
56
+ # @raise [Treaty::Exceptions::Validation] If entity_class is invalid
57
+ # @return [void]
58
+ def use_entity(entity_class)
59
+ validate_entity_class!(entity_class)
60
+ @entity_class = entity_class
61
+ end
62
+
63
+ # Returns the collection of defined attributes
64
+ #
65
+ # @return [Treaty::Entity::Attribute::Collection] Attribute collection
66
+ def collection_of_attributes
67
+ return Treaty::Entity::Attribute::Collection.new if @entity_class.nil?
68
+
69
+ @entity_class.collection_of_attributes
70
+ end
71
+
72
+ # Forwards DSL method calls to internal Entity class
73
+ #
74
+ # Creates an anonymous Entity class on first call, then forwards
75
+ # all DSL methods (string, integer, object, etc.) to it.
76
+ #
77
+ # @param type [Symbol] Attribute type (method name)
78
+ # @param helpers [Array] Helper symbols and arguments
79
+ # @param options [Hash] Attribute options
80
+ # @param block [Proc] Block for nested attributes
81
+ # @return [void]
82
+ def method_missing(type, *helpers, **options, &block)
83
+ @entity_class ||= Class.new(Entity)
84
+
85
+ @entity_class.public_send(type, *helpers, **options, &block)
86
+ end
87
+
88
+ # Checks if method should be handled by method_missing
89
+ #
90
+ # @param name [Symbol] Method name
91
+ # @return [Boolean]
92
+ def respond_to_missing?(name, *)
93
+ super
94
+ end
95
+
96
+ private
97
+
98
+ # Validates that entity_class is a Treaty::Entity::Base subclass
99
+ #
100
+ # @param entity_class [Class] Class to validate
101
+ # @raise [Treaty::Exceptions::Validation] If validation fails
102
+ # @return [void]
103
+ def validate_entity_class!(entity_class)
104
+ return if entity_class.is_a?(Class) && entity_class < Treaty::Entity::Base
105
+
106
+ raise Treaty::Exceptions::Validation,
107
+ I18n.t(
108
+ "treaty.request.factory.invalid_entity_class",
109
+ type: entity_class.class,
110
+ value: entity_class
111
+ )
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Treaty
4
+ module Action
5
+ module Request
6
+ # Validates incoming request parameters against schema.
7
+ #
8
+ # ## Purpose
9
+ #
10
+ # Validates incoming request params against the request schema defined
11
+ # in the treaty version. Runs all validators (required, type, inclusion,
12
+ # format) and transformations (default, cast, transform, as).
13
+ #
14
+ # ## Usage
15
+ #
16
+ # Called internally by:
17
+ # - Versions::Execution::Base (before delegating to service)
18
+ #
19
+ # ## Validation Flow
20
+ #
21
+ # 1. Convert ActionController::Parameters to hash if needed
22
+ # 2. Check if request schema is defined
23
+ # 3. Create dynamic Orchestrator with version's attributes
24
+ # 4. Run validation pipeline (validate + transform)
25
+ # 5. Return validated/transformed params or raise error
26
+ #
27
+ # ## Error Handling
28
+ #
29
+ # Raises Treaty::Exceptions::Validation with detailed messages
30
+ # including attribute path and specific validation failures.
31
+ #
32
+ # ## Example
33
+ #
34
+ # # Typically called via class method:
35
+ # validated = Request::Validator.validate!(
36
+ # params: controller.params,
37
+ # version_factory: version_factory
38
+ # )
39
+ #
40
+ # # validated contains transformed params ready for service
41
+ class Validator
42
+ class << self
43
+ # Validates request parameters
44
+ #
45
+ # @param params [Hash, ActionController::Parameters] Request params
46
+ # @param version_factory [Treaty::Action::Versions::Factory] Version with request schema
47
+ # @return [Hash] Validated and transformed parameters
48
+ # @raise [Treaty::Exceptions::Validation] If validation fails
49
+ def validate!(params:, version_factory:)
50
+ new(params:, version_factory:).validate!
51
+ end
52
+ end
53
+
54
+ # Creates new validator instance
55
+ #
56
+ # @param params [Hash, ActionController::Parameters] Request params
57
+ # @param version_factory [Treaty::Action::Versions::Factory] Version with request schema
58
+ def initialize(params:, version_factory:)
59
+ @params = params
60
+ @version_factory = version_factory
61
+ end
62
+
63
+ # Runs validation pipeline
64
+ #
65
+ # @return [Hash] Validated and transformed parameters
66
+ # @raise [Treaty::Exceptions::Validation] If validation fails
67
+ def validate!
68
+ validate_request_attributes!
69
+ end
70
+
71
+ private
72
+
73
+ # Converts params to plain hash
74
+ #
75
+ # Handles both ActionController::Parameters (with to_unsafe_h)
76
+ # and plain Hash objects.
77
+ #
78
+ # @return [Hash] Plain hash of request data
79
+ def request_data
80
+ @request_data ||= begin
81
+ @params.to_unsafe_h
82
+ rescue NoMethodError
83
+ @params
84
+ end
85
+ end
86
+
87
+ # Validates and transforms request data
88
+ #
89
+ # Creates dynamic Orchestrator class that uses version's request
90
+ # attributes for validation. Returns raw data if no schema defined.
91
+ #
92
+ # @return [Hash] Validated and transformed data
93
+ # @raise [Treaty::Exceptions::Validation] If validation fails
94
+ def validate_request_attributes!
95
+ return request_data unless request_attributes_exist?
96
+
97
+ orchestrator_class = Class.new(Treaty::Entity::Attribute::Validation::Orchestrator::Base) do
98
+ define_method(:collection_of_attributes) do
99
+ @version_factory.request_factory.collection_of_attributes
100
+ end
101
+ end
102
+
103
+ orchestrator_class.validate!(
104
+ version_factory: @version_factory,
105
+ data: request_data
106
+ )
107
+ end
108
+
109
+ # Checks if request schema is defined for this version
110
+ #
111
+ # @return [Boolean] True if request attributes exist
112
+ def request_attributes_exist?
113
+ return false if @version_factory.request_factory&.collection_of_attributes&.empty?
114
+
115
+ @version_factory.request_factory.collection_of_attributes.exists?
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end