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.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/treaty/action/base.rb +11 -0
- data/lib/treaty/action/context/callable.rb +90 -0
- data/lib/treaty/action/context/dsl.rb +56 -0
- data/lib/treaty/action/context/workspace.rb +92 -0
- data/lib/treaty/action/executor/inventory.rb +136 -0
- data/lib/treaty/{info/rest → action/info}/builder.rb +2 -2
- data/lib/treaty/{info/rest → action/info}/dsl.rb +2 -2
- data/lib/treaty/{info/rest → action/info}/result.rb +2 -2
- data/lib/treaty/action/inventory/collection.rb +77 -0
- data/lib/treaty/action/inventory/factory.rb +108 -0
- data/lib/treaty/action/inventory/inventory.rb +146 -0
- data/lib/treaty/action/request/attribute/attribute.rb +76 -0
- data/lib/treaty/action/request/attribute/builder.rb +98 -0
- data/lib/treaty/action/request/entity.rb +78 -0
- data/lib/treaty/action/request/factory.rb +116 -0
- data/lib/treaty/action/request/validator.rb +120 -0
- data/lib/treaty/action/response/attribute/attribute.rb +79 -0
- data/lib/treaty/action/response/attribute/builder.rb +96 -0
- data/lib/treaty/action/response/entity.rb +79 -0
- data/lib/treaty/action/response/factory.rb +129 -0
- data/lib/treaty/action/response/validator.rb +111 -0
- data/lib/treaty/action/result.rb +81 -0
- data/lib/treaty/action/versions/collection.rb +47 -0
- data/lib/treaty/action/versions/dsl.rb +116 -0
- data/lib/treaty/action/versions/execution/request.rb +287 -0
- data/lib/treaty/action/versions/executor.rb +61 -0
- data/lib/treaty/action/versions/factory.rb +253 -0
- data/lib/treaty/action/versions/resolver.rb +150 -0
- data/lib/treaty/action/versions/semantic.rb +64 -0
- data/lib/treaty/action/versions/workspace.rb +106 -0
- data/lib/treaty/action.rb +31 -0
- data/lib/treaty/controller/dsl.rb +1 -1
- data/lib/treaty/entity/attribute/base.rb +1 -1
- data/lib/treaty/entity/attribute/builder/base.rb +1 -1
- data/lib/treaty/entity/attribute/dsl.rb +1 -1
- data/lib/treaty/entity/base.rb +1 -1
- data/lib/treaty/entity/builder.rb +62 -5
- data/lib/treaty/version.rb +1 -1
- metadata +32 -31
- data/lib/treaty/base.rb +0 -9
- data/lib/treaty/context/callable.rb +0 -26
- data/lib/treaty/context/dsl.rb +0 -12
- data/lib/treaty/context/workspace.rb +0 -32
- data/lib/treaty/executor/inventory.rb +0 -122
- data/lib/treaty/inventory/collection.rb +0 -71
- data/lib/treaty/inventory/factory.rb +0 -91
- data/lib/treaty/inventory/inventory.rb +0 -92
- data/lib/treaty/request/attribute/attribute.rb +0 -25
- data/lib/treaty/request/attribute/builder.rb +0 -46
- data/lib/treaty/request/entity.rb +0 -33
- data/lib/treaty/request/factory.rb +0 -81
- data/lib/treaty/request/validator.rb +0 -60
- data/lib/treaty/response/attribute/attribute.rb +0 -25
- data/lib/treaty/response/attribute/builder.rb +0 -46
- data/lib/treaty/response/entity.rb +0 -33
- data/lib/treaty/response/factory.rb +0 -87
- data/lib/treaty/response/validator.rb +0 -53
- data/lib/treaty/result.rb +0 -23
- data/lib/treaty/versions/collection.rb +0 -15
- data/lib/treaty/versions/dsl.rb +0 -42
- data/lib/treaty/versions/execution/request.rb +0 -177
- data/lib/treaty/versions/executor.rb +0 -14
- data/lib/treaty/versions/factory.rb +0 -112
- data/lib/treaty/versions/resolver.rb +0 -70
- data/lib/treaty/versions/semantic.rb +0 -22
- data/lib/treaty/versions/workspace.rb +0 -43
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
module Action
|
|
5
|
+
module Response
|
|
6
|
+
module Attribute
|
|
7
|
+
# Response-specific attribute definition.
|
|
8
|
+
#
|
|
9
|
+
# ## Purpose
|
|
10
|
+
#
|
|
11
|
+
# Extends Entity::Attribute::Base with Response-specific behavior.
|
|
12
|
+
# Key difference: attributes are **optional by default**.
|
|
13
|
+
#
|
|
14
|
+
# ## Default Behavior
|
|
15
|
+
#
|
|
16
|
+
# Unlike Request attributes (required by default), Response attributes
|
|
17
|
+
# default to `required: false`. This allows flexible response structures
|
|
18
|
+
# where not all fields must be present.
|
|
19
|
+
#
|
|
20
|
+
# ## Usage
|
|
21
|
+
#
|
|
22
|
+
# Created internally by:
|
|
23
|
+
# - Response::Attribute::Builder (when defining nested attributes)
|
|
24
|
+
# - Response::Entity (when defining top-level attributes)
|
|
25
|
+
#
|
|
26
|
+
# ## Nesting
|
|
27
|
+
#
|
|
28
|
+
# Object and array types create nested builders:
|
|
29
|
+
#
|
|
30
|
+
# response 200 do
|
|
31
|
+
# object :post do # Creates Attribute with nested builder
|
|
32
|
+
# string :title # Nested attribute (optional by default)
|
|
33
|
+
# array :comments do # Nested array
|
|
34
|
+
# object :_self do # Array element definition
|
|
35
|
+
# string :text
|
|
36
|
+
# end
|
|
37
|
+
# end
|
|
38
|
+
# end
|
|
39
|
+
# end
|
|
40
|
+
#
|
|
41
|
+
# ## Example
|
|
42
|
+
#
|
|
43
|
+
# # These are equivalent:
|
|
44
|
+
# string :title # optional by default
|
|
45
|
+
# string :title, :optional # explicit optional
|
|
46
|
+
#
|
|
47
|
+
# # Must explicitly mark required:
|
|
48
|
+
# string :id, :required
|
|
49
|
+
class Attribute < Treaty::Entity::Attribute::Base
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
# Sets default required behavior for response attributes
|
|
53
|
+
#
|
|
54
|
+
# Response attributes are optional by default (is: false).
|
|
55
|
+
# This can be overridden with `:required` helper or `required: true`.
|
|
56
|
+
#
|
|
57
|
+
# @return [void]
|
|
58
|
+
def apply_defaults!
|
|
59
|
+
@options[:required] ||= { is: false, message: nil }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Creates nested builder for object/array type processing
|
|
63
|
+
#
|
|
64
|
+
# When a block is given to object or array attributes,
|
|
65
|
+
# creates a Builder to process the nested attribute definitions.
|
|
66
|
+
#
|
|
67
|
+
# @param block [Proc] Block containing nested attribute definitions
|
|
68
|
+
# @return [void]
|
|
69
|
+
def process_nested_attributes(&block)
|
|
70
|
+
return unless object_or_array?
|
|
71
|
+
|
|
72
|
+
builder = Builder.new(collection_of_attributes, @nesting_level + 1)
|
|
73
|
+
builder.instance_eval(&block)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
module Action
|
|
5
|
+
module Response
|
|
6
|
+
module Attribute
|
|
7
|
+
# DSL builder for defining response attributes.
|
|
8
|
+
#
|
|
9
|
+
# ## Purpose
|
|
10
|
+
#
|
|
11
|
+
# Provides Response-specific implementation of the attribute builder.
|
|
12
|
+
# Creates Response::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
|
+
# - Response::Attribute::Attribute (when processing nested object/array blocks)
|
|
26
|
+
#
|
|
27
|
+
# ## DSL Example
|
|
28
|
+
#
|
|
29
|
+
# response 201 do
|
|
30
|
+
# object :post do
|
|
31
|
+
# string :id
|
|
32
|
+
# string :title
|
|
33
|
+
# datetime :created_at
|
|
34
|
+
# end
|
|
35
|
+
# end
|
|
36
|
+
#
|
|
37
|
+
# ## Methods
|
|
38
|
+
#
|
|
39
|
+
# Implements abstract methods from base class:
|
|
40
|
+
# - `create_attribute` - Creates Response::Attribute::Attribute
|
|
41
|
+
# - `deep_copy_attribute` - Deep copies attribute for use_entity support
|
|
42
|
+
class Builder < Treaty::Entity::Attribute::Builder::Base
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
# Creates a new response attribute instance
|
|
46
|
+
#
|
|
47
|
+
# Called by base class when defining attributes via DSL.
|
|
48
|
+
#
|
|
49
|
+
# @param name [Symbol] Attribute name
|
|
50
|
+
# @param type [Symbol] Attribute type (:string, :integer, :object, etc.)
|
|
51
|
+
# @param helpers [Array<Symbol>] Helper symbols (:required, :optional)
|
|
52
|
+
# @param nesting_level [Integer] Current nesting depth
|
|
53
|
+
# @param options [Hash] Attribute options (default:, format:, etc.)
|
|
54
|
+
# @param block [Proc] Block for nested attributes (object/array)
|
|
55
|
+
# @return [Treaty::Action::Response::Attribute::Attribute] Created attribute instance
|
|
56
|
+
def create_attribute(name, type, *helpers, nesting_level:, **options, &block)
|
|
57
|
+
Attribute.new(
|
|
58
|
+
name,
|
|
59
|
+
type,
|
|
60
|
+
*helpers,
|
|
61
|
+
nesting_level:,
|
|
62
|
+
**options,
|
|
63
|
+
&block
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Deep copies an attribute with adjusted nesting level
|
|
68
|
+
#
|
|
69
|
+
# Used when copying attributes from Entity classes via use_entity.
|
|
70
|
+
# Recursively copies nested attributes for object/array types.
|
|
71
|
+
#
|
|
72
|
+
# @param source_attribute [Treaty::Entity::Attribute::Base] Source attribute to copy
|
|
73
|
+
# @param new_nesting_level [Integer] Nesting level for copied attribute
|
|
74
|
+
# @return [Treaty::Action::Response::Attribute::Attribute] Deep copied attribute
|
|
75
|
+
def deep_copy_attribute(source_attribute, new_nesting_level) # rubocop:disable Metrics/MethodLength
|
|
76
|
+
copied = Attribute.new(
|
|
77
|
+
source_attribute.name,
|
|
78
|
+
source_attribute.type,
|
|
79
|
+
nesting_level: new_nesting_level,
|
|
80
|
+
**deep_copy_options(source_attribute.options)
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return copied unless source_attribute.nested?
|
|
84
|
+
|
|
85
|
+
source_attribute.collection_of_attributes.each do |nested_source|
|
|
86
|
+
nested_copied = deep_copy_attribute(nested_source, new_nesting_level + 1)
|
|
87
|
+
copied.collection_of_attributes << nested_copied
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
copied
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
module Action
|
|
5
|
+
module Response
|
|
6
|
+
# Internal entity class for response attribute definitions.
|
|
7
|
+
#
|
|
8
|
+
# ## Purpose
|
|
9
|
+
#
|
|
10
|
+
# Provides DSL interface for defining response attributes when using
|
|
11
|
+
# inline block syntax in treaty definitions. Serves as the anonymous
|
|
12
|
+
# class base when `response STATUS do ... end` is used.
|
|
13
|
+
#
|
|
14
|
+
# ## Usage
|
|
15
|
+
#
|
|
16
|
+
# Created internally by:
|
|
17
|
+
# - Response::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
|
+
# Response::Entity creates Response-specific attributes with:
|
|
30
|
+
# - Optional by default behavior
|
|
31
|
+
# - Response::Attribute::Attribute instances
|
|
32
|
+
#
|
|
33
|
+
# ## Example
|
|
34
|
+
#
|
|
35
|
+
# # When you write:
|
|
36
|
+
# version 1 do
|
|
37
|
+
# response 201 do
|
|
38
|
+
# object :post do
|
|
39
|
+
# string :id
|
|
40
|
+
# string :title
|
|
41
|
+
# end
|
|
42
|
+
# end
|
|
43
|
+
# end
|
|
44
|
+
#
|
|
45
|
+
# # Factory creates: Class.new(Response::Entity)
|
|
46
|
+
# # and calls string, object etc. on it
|
|
47
|
+
class Entity
|
|
48
|
+
include Treaty::Entity::Attribute::DSL
|
|
49
|
+
|
|
50
|
+
class << self
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
# Creates response-specific attribute instances
|
|
54
|
+
#
|
|
55
|
+
# Called by DSL methods (string, integer, etc.) to create
|
|
56
|
+
# Response::Attribute::Attribute instead of generic attributes.
|
|
57
|
+
#
|
|
58
|
+
# @param name [Symbol] Attribute name
|
|
59
|
+
# @param type [Symbol] Attribute type
|
|
60
|
+
# @param helpers [Array<Symbol>] Helper symbols (:required, :optional)
|
|
61
|
+
# @param nesting_level [Integer] Current nesting depth
|
|
62
|
+
# @param options [Hash] Attribute options
|
|
63
|
+
# @param block [Proc] Block for nested attributes
|
|
64
|
+
# @return [Treaty::Action::Response::Attribute::Attribute] Created attribute
|
|
65
|
+
def create_attribute(name, type, *helpers, nesting_level:, **options, &block)
|
|
66
|
+
Attribute::Attribute.new(
|
|
67
|
+
name,
|
|
68
|
+
type,
|
|
69
|
+
*helpers,
|
|
70
|
+
nesting_level:,
|
|
71
|
+
**options,
|
|
72
|
+
&block
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
module Action
|
|
5
|
+
module Response
|
|
6
|
+
# Factory for creating response attribute collections.
|
|
7
|
+
#
|
|
8
|
+
# ## Purpose
|
|
9
|
+
#
|
|
10
|
+
# Captures response 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
|
+
# ## Difference from Request::Factory
|
|
15
|
+
#
|
|
16
|
+
# Response::Factory stores HTTP status code along with attributes.
|
|
17
|
+
# This allows treaty to validate responses against expected status.
|
|
18
|
+
#
|
|
19
|
+
# ## Usage
|
|
20
|
+
#
|
|
21
|
+
# Created internally by:
|
|
22
|
+
# - Versions::Factory (when `response STATUS do ... end` is called)
|
|
23
|
+
#
|
|
24
|
+
# Consumed by:
|
|
25
|
+
# - Response::Validator (to validate service output)
|
|
26
|
+
# - Info::Builder (to build response schema information)
|
|
27
|
+
# - Versions::Execution::Base (to set response status)
|
|
28
|
+
#
|
|
29
|
+
# ## Definition Modes
|
|
30
|
+
#
|
|
31
|
+
# ### Inline Block Mode
|
|
32
|
+
#
|
|
33
|
+
# response 201 do
|
|
34
|
+
# object :post do
|
|
35
|
+
# string :id
|
|
36
|
+
# string :title
|
|
37
|
+
# end
|
|
38
|
+
# end
|
|
39
|
+
#
|
|
40
|
+
# ### Entity Class Mode
|
|
41
|
+
#
|
|
42
|
+
# response 201, Posts::Create::ResponseEntity
|
|
43
|
+
#
|
|
44
|
+
# ## Example
|
|
45
|
+
#
|
|
46
|
+
# factory = Response::Factory.new(201)
|
|
47
|
+
# factory.status # => 201
|
|
48
|
+
# factory.object :post do
|
|
49
|
+
# factory.string :id
|
|
50
|
+
# end
|
|
51
|
+
# factory.collection_of_attributes # => Collection with post attribute
|
|
52
|
+
class Factory
|
|
53
|
+
# @return [Integer] HTTP status code for this response
|
|
54
|
+
attr_reader :status
|
|
55
|
+
|
|
56
|
+
# Creates new response factory with HTTP status
|
|
57
|
+
#
|
|
58
|
+
# @param status [Integer] HTTP status code (200, 201, 404, etc.)
|
|
59
|
+
def initialize(status)
|
|
60
|
+
@status = status
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Registers an Entity class for response schema
|
|
64
|
+
#
|
|
65
|
+
# Use this to reference a pre-defined Entity class instead of
|
|
66
|
+
# inline attribute definitions.
|
|
67
|
+
#
|
|
68
|
+
# @param entity_class [Class] Must be Treaty::Entity::Base subclass
|
|
69
|
+
# @raise [Treaty::Exceptions::Validation] If entity_class is invalid
|
|
70
|
+
# @return [void]
|
|
71
|
+
def use_entity(entity_class)
|
|
72
|
+
validate_entity_class!(entity_class)
|
|
73
|
+
@entity_class = entity_class
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Returns the collection of defined attributes
|
|
77
|
+
#
|
|
78
|
+
# @return [Treaty::Entity::Attribute::Collection] Attribute collection
|
|
79
|
+
def collection_of_attributes
|
|
80
|
+
return Treaty::Entity::Attribute::Collection.new if @entity_class.nil?
|
|
81
|
+
|
|
82
|
+
@entity_class.collection_of_attributes
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Forwards DSL method calls to internal Entity class
|
|
86
|
+
#
|
|
87
|
+
# Creates an anonymous Entity class on first call, then forwards
|
|
88
|
+
# all DSL methods (string, integer, object, etc.) to it.
|
|
89
|
+
#
|
|
90
|
+
# @param type [Symbol] Attribute type (method name)
|
|
91
|
+
# @param helpers [Array] Helper symbols and arguments
|
|
92
|
+
# @param options [Hash] Attribute options
|
|
93
|
+
# @param block [Proc] Block for nested attributes
|
|
94
|
+
# @return [void]
|
|
95
|
+
def method_missing(type, *helpers, **options, &block)
|
|
96
|
+
@entity_class ||= Class.new(Entity)
|
|
97
|
+
|
|
98
|
+
@entity_class.public_send(type, *helpers, **options, &block)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Checks if method should be handled by method_missing
|
|
102
|
+
#
|
|
103
|
+
# @param name [Symbol] Method name
|
|
104
|
+
# @return [Boolean]
|
|
105
|
+
def respond_to_missing?(name, *)
|
|
106
|
+
super
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
# Validates that entity_class is a Treaty::Entity::Base subclass
|
|
112
|
+
#
|
|
113
|
+
# @param entity_class [Class] Class to validate
|
|
114
|
+
# @raise [Treaty::Exceptions::Validation] If validation fails
|
|
115
|
+
# @return [void]
|
|
116
|
+
def validate_entity_class!(entity_class)
|
|
117
|
+
return if entity_class.is_a?(Class) && entity_class < Treaty::Entity::Base
|
|
118
|
+
|
|
119
|
+
raise Treaty::Exceptions::Validation,
|
|
120
|
+
I18n.t(
|
|
121
|
+
"treaty.response.factory.invalid_entity_class",
|
|
122
|
+
type: entity_class.class,
|
|
123
|
+
value: entity_class
|
|
124
|
+
)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
module Action
|
|
5
|
+
module Response
|
|
6
|
+
# Validates service response data against schema.
|
|
7
|
+
#
|
|
8
|
+
# ## Purpose
|
|
9
|
+
#
|
|
10
|
+
# Validates response data from service execution against the response
|
|
11
|
+
# schema defined in the treaty version. Ensures service output matches
|
|
12
|
+
# the documented API contract.
|
|
13
|
+
#
|
|
14
|
+
# ## Usage
|
|
15
|
+
#
|
|
16
|
+
# Called internally by:
|
|
17
|
+
# - Versions::Execution::Response (after service execution)
|
|
18
|
+
#
|
|
19
|
+
# ## Validation Flow
|
|
20
|
+
#
|
|
21
|
+
# 1. Check if response schema is defined
|
|
22
|
+
# 2. Create dynamic Orchestrator with version's response attributes
|
|
23
|
+
# 3. Run validation pipeline (validate + transform)
|
|
24
|
+
# 4. Return validated/transformed data or raise error
|
|
25
|
+
#
|
|
26
|
+
# ## Error Handling
|
|
27
|
+
#
|
|
28
|
+
# Raises Treaty::Exceptions::Validation with detailed messages
|
|
29
|
+
# including attribute path and specific validation failures.
|
|
30
|
+
#
|
|
31
|
+
# ## Difference from Request::Validator
|
|
32
|
+
#
|
|
33
|
+
# - Validates service output, not controller params
|
|
34
|
+
# - Response attributes are optional by default
|
|
35
|
+
# - Used after service execution, not before
|
|
36
|
+
#
|
|
37
|
+
# ## Example
|
|
38
|
+
#
|
|
39
|
+
# # Typically called via class method:
|
|
40
|
+
# validated = Response::Validator.validate!(
|
|
41
|
+
# version_factory: version_factory,
|
|
42
|
+
# response_data: service_result
|
|
43
|
+
# )
|
|
44
|
+
#
|
|
45
|
+
# # validated contains transformed response for client
|
|
46
|
+
class Validator
|
|
47
|
+
class << self
|
|
48
|
+
# Validates response data
|
|
49
|
+
#
|
|
50
|
+
# @param version_factory [Treaty::Action::Versions::Factory] Version with response schema
|
|
51
|
+
# @param response_data [Hash] Response data from service
|
|
52
|
+
# @return [Hash] Validated and transformed response
|
|
53
|
+
# @raise [Treaty::Exceptions::Validation] If validation fails
|
|
54
|
+
def validate!(version_factory:, response_data: {})
|
|
55
|
+
new(version_factory:, response_data:).validate!
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Creates new validator instance
|
|
60
|
+
#
|
|
61
|
+
# @param version_factory [Treaty::Action::Versions::Factory] Version with response schema
|
|
62
|
+
# @param response_data [Hash] Response data to validate
|
|
63
|
+
def initialize(version_factory:, response_data: {})
|
|
64
|
+
@version_factory = version_factory
|
|
65
|
+
@response_data = response_data
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Runs validation pipeline
|
|
69
|
+
#
|
|
70
|
+
# @return [Hash] Validated and transformed response
|
|
71
|
+
# @raise [Treaty::Exceptions::Validation] If validation fails
|
|
72
|
+
def validate!
|
|
73
|
+
validate_response_attributes!
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
# Validates and transforms response data
|
|
79
|
+
#
|
|
80
|
+
# Creates dynamic Orchestrator class that uses version's response
|
|
81
|
+
# attributes for validation. Returns raw data if no schema defined.
|
|
82
|
+
#
|
|
83
|
+
# @return [Hash] Validated and transformed data
|
|
84
|
+
# @raise [Treaty::Exceptions::Validation] If validation fails
|
|
85
|
+
def validate_response_attributes!
|
|
86
|
+
return @response_data unless response_attributes_exist?
|
|
87
|
+
|
|
88
|
+
orchestrator_class = Class.new(Treaty::Entity::Attribute::Validation::Orchestrator::Base) do
|
|
89
|
+
define_method(:collection_of_attributes) do
|
|
90
|
+
@version_factory.response_factory.collection_of_attributes
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
orchestrator_class.validate!(
|
|
95
|
+
version_factory: @version_factory,
|
|
96
|
+
data: @response_data
|
|
97
|
+
)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Checks if response schema is defined for this version
|
|
101
|
+
#
|
|
102
|
+
# @return [Boolean] True if response attributes exist
|
|
103
|
+
def response_attributes_exist?
|
|
104
|
+
return false if @version_factory.response_factory&.collection_of_attributes&.empty?
|
|
105
|
+
|
|
106
|
+
@version_factory.response_factory.collection_of_attributes.exists?
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
module Action
|
|
5
|
+
# Value object returned from treaty execution.
|
|
6
|
+
#
|
|
7
|
+
# ## Purpose
|
|
8
|
+
#
|
|
9
|
+
# Encapsulates the result of a treaty call, containing validated
|
|
10
|
+
# response data, HTTP status code, and resolved version. This is
|
|
11
|
+
# the primary return type from `Treaty.call!`.
|
|
12
|
+
#
|
|
13
|
+
# ## Usage
|
|
14
|
+
#
|
|
15
|
+
# Created by:
|
|
16
|
+
# - Versions::Workspace (at the end of treaty execution)
|
|
17
|
+
#
|
|
18
|
+
# Consumed by:
|
|
19
|
+
# - Controller::DSL (to render JSON response)
|
|
20
|
+
# - Test assertions (to verify treaty behavior)
|
|
21
|
+
#
|
|
22
|
+
# ## Attributes
|
|
23
|
+
#
|
|
24
|
+
# | Attribute | Description |
|
|
25
|
+
# |-----------|-------------|
|
|
26
|
+
# | `data` | Validated and transformed response hash |
|
|
27
|
+
# | `status` | HTTP status code from response definition |
|
|
28
|
+
# | `version` | Resolved Gem::Version object |
|
|
29
|
+
#
|
|
30
|
+
# ## Example
|
|
31
|
+
#
|
|
32
|
+
# result = Posts::CreateTreaty.call!(
|
|
33
|
+
# version: "1",
|
|
34
|
+
# params: { post: { title: "Hello" } }
|
|
35
|
+
# )
|
|
36
|
+
#
|
|
37
|
+
# result.data # => { post: { id: "abc", title: "Hello" } }
|
|
38
|
+
# result.status # => 201
|
|
39
|
+
# result.version # => Gem::Version.new("1")
|
|
40
|
+
#
|
|
41
|
+
# # In controller:
|
|
42
|
+
# render json: result.data, status: result.status
|
|
43
|
+
class Result
|
|
44
|
+
# @return [Hash] Validated response data
|
|
45
|
+
attr_reader :data
|
|
46
|
+
|
|
47
|
+
# @return [Integer] HTTP status code
|
|
48
|
+
attr_reader :status
|
|
49
|
+
|
|
50
|
+
# @return [Gem::Version] Resolved API version
|
|
51
|
+
attr_reader :version
|
|
52
|
+
|
|
53
|
+
# Creates a new result instance
|
|
54
|
+
#
|
|
55
|
+
# @param data [Hash] Validated response data
|
|
56
|
+
# @param status [Integer] HTTP status code
|
|
57
|
+
# @param version [Gem::Version] Resolved version
|
|
58
|
+
def initialize(data:, status:, version:)
|
|
59
|
+
@data = data
|
|
60
|
+
@status = status
|
|
61
|
+
@version = version
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Returns human-readable representation for debugging
|
|
65
|
+
#
|
|
66
|
+
# @return [String] Inspection string with all attributes
|
|
67
|
+
def inspect
|
|
68
|
+
"#<#{self.class.name} #{draw_result}>"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
# Formats attributes for inspect output
|
|
74
|
+
#
|
|
75
|
+
# @return [String] Formatted attribute string
|
|
76
|
+
def draw_result
|
|
77
|
+
"@data=#{@data.inspect}, @status=#{@status.inspect}, @version=#{@version.inspect}"
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
module Action
|
|
5
|
+
module Versions
|
|
6
|
+
# Collection wrapper for sets of version factories.
|
|
7
|
+
#
|
|
8
|
+
# ## Purpose
|
|
9
|
+
#
|
|
10
|
+
# Provides a unified interface for working with collections of version factories.
|
|
11
|
+
# Uses Ruby Set internally for uniqueness but exposes Array-like interface.
|
|
12
|
+
#
|
|
13
|
+
# ## Usage
|
|
14
|
+
#
|
|
15
|
+
# Used internally by:
|
|
16
|
+
# - Versions::DSL (to store version factories)
|
|
17
|
+
# - Versions::Resolver (to find matching version)
|
|
18
|
+
# - Info::Builder (to build version information)
|
|
19
|
+
#
|
|
20
|
+
# ## Methods
|
|
21
|
+
#
|
|
22
|
+
# Delegates common collection methods to internal Set:
|
|
23
|
+
# - `<<` - Add version factory
|
|
24
|
+
# - `map` - Iteration with transformation
|
|
25
|
+
# - `find` - Access by condition
|
|
26
|
+
#
|
|
27
|
+
# ## Example
|
|
28
|
+
#
|
|
29
|
+
# collection = Collection.new
|
|
30
|
+
# collection << Factory.new(version: 1, default: true)
|
|
31
|
+
# collection << Factory.new(version: 2, default: false)
|
|
32
|
+
# collection.find { |f| f.default_result } # => Factory(v1)
|
|
33
|
+
class Collection
|
|
34
|
+
extend Forwardable
|
|
35
|
+
|
|
36
|
+
def_delegators :@collection, :<<, :map, :find
|
|
37
|
+
|
|
38
|
+
# Creates a new collection instance
|
|
39
|
+
#
|
|
40
|
+
# @param collection [Set] Initial collection (default: empty Set)
|
|
41
|
+
def initialize(collection = Set.new)
|
|
42
|
+
@collection = collection
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|