treaty 0.9.0 → 0.10.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 +6 -6
- data/config/locales/en.yml +1 -1
- data/lib/treaty/exceptions/base.rb +2 -0
- data/lib/treaty/exceptions/specified_version_not_found.rb +117 -0
- data/lib/treaty/exceptions/version_not_found.rb +160 -0
- data/lib/treaty/version.rb +1 -1
- data/lib/treaty/versions/resolver.rb +13 -13
- data/lib/treaty/versions/workspace.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f3115e472c191d30a9c5c8bea6bceb1ed6674437c4e82053817da35e4e94ed4f
|
|
4
|
+
data.tar.gz: e43b55d9b75c5d2ded71686319a773a814863c10e7d6c25a030151d45883ceeb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f83bdae2ce72bb4f47773ccfa3911be30fb55a5710d211778ce725d487a6b09044c838d18fa23299607ae2ade4cbe671a189ed7d0969b57f1ab7ceaf912c8680
|
|
7
|
+
data.tar.gz: 01b3769426d4b04b1fe18441c3d943aa36ad8ccca077e7f0d4bf7c5ab1179ec88ae3343c4cd09a9274a87bcf97a88707479458bf7f9fc64fac6936770f0fce06
|
data/README.md
CHANGED
|
@@ -13,18 +13,18 @@
|
|
|
13
13
|
</div>
|
|
14
14
|
|
|
15
15
|
> [!WARNING]
|
|
16
|
-
> **Development Status**: Treaty is currently under active development in the 0.x version series. Breaking changes may occur between minor versions (0.x) as we refine the API and add new features. The library will stabilize with the 1.0 release. We recommend pinning to specific patch versions in your Gemfile (e.g., `gem "treaty", "~> 0.
|
|
16
|
+
> **Development Status**: Treaty is currently under active development in the 0.x version series. Breaking changes may occur between minor versions (0.x) as we refine the API and add new features. The library will stabilize with the 1.0 release. We recommend pinning to specific patch versions in your Gemfile (e.g., `gem "treaty", "~> 0.9.0"`) until the 1.0 release.
|
|
17
17
|
|
|
18
18
|
## 📚 Documentation
|
|
19
19
|
|
|
20
20
|
Explore comprehensive guides and documentation at [docs](./docs):
|
|
21
21
|
|
|
22
|
-
- [Getting Started](./docs/getting-started.md) -
|
|
23
|
-
- [Core Concepts](./docs/core-concepts.md) -
|
|
24
|
-
- [API Reference](./docs/api-reference.md) -
|
|
25
|
-
- [Examples](./docs/examples.md) -
|
|
22
|
+
- [Getting Started](./docs/getting-started.md) - Installation and basic setup
|
|
23
|
+
- [Core Concepts](./docs/core-concepts.md) - Fundamental concepts and architecture
|
|
24
|
+
- [API Reference](./docs/api-reference.md) - Complete API documentation
|
|
25
|
+
- [Examples](./docs/examples.md) - Real-world usage examples
|
|
26
26
|
- [Internationalization](./docs/internationalization.md) - I18n and multilingual support
|
|
27
|
-
- [Full Documentation Index](./docs/README.md) -
|
|
27
|
+
- [Full Documentation Index](./docs/README.md) - Complete documentation index
|
|
28
28
|
|
|
29
29
|
## 💡 Why Treaty?
|
|
30
30
|
|
data/config/locales/en.yml
CHANGED
|
@@ -84,7 +84,7 @@ en:
|
|
|
84
84
|
versioning:
|
|
85
85
|
# Version resolver
|
|
86
86
|
resolver:
|
|
87
|
-
|
|
87
|
+
specified_version_required: "Specified version is required for validation"
|
|
88
88
|
version_not_found: "Version %{version} not found in treaty definition"
|
|
89
89
|
version_deprecated: "Version %{version} is deprecated and cannot be used"
|
|
90
90
|
|
|
@@ -35,6 +35,8 @@ module Treaty
|
|
|
35
35
|
# - Validation - Attribute validation errors
|
|
36
36
|
# - Execution - Service execution errors
|
|
37
37
|
# - Deprecated - API version deprecation
|
|
38
|
+
# - SpecifiedVersionNotFound - No version specified and no default configured
|
|
39
|
+
# - VersionNotFound - Requested version doesn't exist
|
|
38
40
|
# - Strategy - Invalid strategy specification
|
|
39
41
|
# - ClassName - Treaty class not found
|
|
40
42
|
# - MethodName - Unknown method in DSL
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
module Exceptions
|
|
5
|
+
# Raised when no API version is specified and no default version is configured
|
|
6
|
+
#
|
|
7
|
+
# ## Purpose
|
|
8
|
+
#
|
|
9
|
+
# Prevents treaty execution when the client doesn't specify a version
|
|
10
|
+
# and the treaty hasn't defined a default version to fall back to.
|
|
11
|
+
# Enforces explicit version selection for API contracts.
|
|
12
|
+
#
|
|
13
|
+
# ## Usage
|
|
14
|
+
#
|
|
15
|
+
# Raised automatically during version resolution in two scenarios:
|
|
16
|
+
#
|
|
17
|
+
# ### Scenario 1: No Version Specified, No Default Configured
|
|
18
|
+
# ```ruby
|
|
19
|
+
# class PostsTreaty < ApplicationTreaty
|
|
20
|
+
# version 1 do
|
|
21
|
+
# # No default: true specified
|
|
22
|
+
# request { string :title }
|
|
23
|
+
# response(200) { object :post }
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
# version 2 do
|
|
27
|
+
# request { string :title }
|
|
28
|
+
# response(200) { object :post }
|
|
29
|
+
# end
|
|
30
|
+
# end
|
|
31
|
+
#
|
|
32
|
+
# # Client request without version header
|
|
33
|
+
# PostsTreaty.call!(version: nil, params: { title: "Test" })
|
|
34
|
+
# # => Raises Treaty::Exceptions::SpecifiedVersionNotFound
|
|
35
|
+
# # => "Specified version is required for validation"
|
|
36
|
+
# ```
|
|
37
|
+
#
|
|
38
|
+
# ### Scenario 2: Empty Version String
|
|
39
|
+
# ```ruby
|
|
40
|
+
# PostsTreaty.call!(version: "", params: { title: "Test" })
|
|
41
|
+
# # => Raises Treaty::Exceptions::SpecifiedVersionNotFound
|
|
42
|
+
# ```
|
|
43
|
+
#
|
|
44
|
+
# ### Prevention: Define Default Version
|
|
45
|
+
# ```ruby
|
|
46
|
+
# class PostsTreaty < ApplicationTreaty
|
|
47
|
+
# version 1 do
|
|
48
|
+
# request { string :title }
|
|
49
|
+
# response(200) { object :post }
|
|
50
|
+
# end
|
|
51
|
+
#
|
|
52
|
+
# version 2, default: true do # Marks version 2 as default
|
|
53
|
+
# request { string :title }
|
|
54
|
+
# response(200) { object :post }
|
|
55
|
+
# end
|
|
56
|
+
# end
|
|
57
|
+
#
|
|
58
|
+
# # Now works without explicit version
|
|
59
|
+
# PostsTreaty.call!(version: nil, params: { title: "Test" })
|
|
60
|
+
# # => Uses version 2 by default
|
|
61
|
+
# ```
|
|
62
|
+
#
|
|
63
|
+
# ## Integration
|
|
64
|
+
#
|
|
65
|
+
# Can be rescued by application controllers to return appropriate HTTP status:
|
|
66
|
+
#
|
|
67
|
+
# ```ruby
|
|
68
|
+
# rescue_from Treaty::Exceptions::SpecifiedVersionNotFound, with: :render_version_required
|
|
69
|
+
#
|
|
70
|
+
# def render_version_required(exception)
|
|
71
|
+
# render json: {
|
|
72
|
+
# error: exception.message,
|
|
73
|
+
# hint: "Please specify an API version in the request header"
|
|
74
|
+
# }, status: :bad_request # HTTP 400
|
|
75
|
+
# end
|
|
76
|
+
# ```
|
|
77
|
+
#
|
|
78
|
+
# ## HTTP Status
|
|
79
|
+
#
|
|
80
|
+
# Typically returns HTTP 400 Bad Request, indicating that the client
|
|
81
|
+
# failed to provide required version information.
|
|
82
|
+
#
|
|
83
|
+
# ## Best Practices
|
|
84
|
+
#
|
|
85
|
+
# ### For API Providers
|
|
86
|
+
#
|
|
87
|
+
# 1. **Always define a default version** for backward compatibility:
|
|
88
|
+
# ```ruby
|
|
89
|
+
# version 1, default: true do
|
|
90
|
+
# # ...
|
|
91
|
+
# end
|
|
92
|
+
# ```
|
|
93
|
+
#
|
|
94
|
+
# 2. **Document version requirements** in API documentation
|
|
95
|
+
#
|
|
96
|
+
# 3. **Provide helpful error messages** in rescue handlers
|
|
97
|
+
#
|
|
98
|
+
# ### For API Clients
|
|
99
|
+
#
|
|
100
|
+
# 1. **Always specify version explicitly** in production code
|
|
101
|
+
# 2. **Don't rely on default versions** for critical applications
|
|
102
|
+
# 3. **Handle this exception** with version selection logic
|
|
103
|
+
#
|
|
104
|
+
# ## Difference from VersionNotFound
|
|
105
|
+
#
|
|
106
|
+
# - **SpecifiedVersionNotFound**: No version specified (nil/blank)
|
|
107
|
+
# - **VersionNotFound**: Specific version specified but doesn't exist
|
|
108
|
+
#
|
|
109
|
+
# ## Version Selection Flow
|
|
110
|
+
#
|
|
111
|
+
# 1. Client provides version → Use specified version
|
|
112
|
+
# 2. Client provides no version → Look for default version
|
|
113
|
+
# 3. No default version configured → Raise SpecifiedVersionNotFound
|
|
114
|
+
class SpecifiedVersionNotFound < Base
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
module Exceptions
|
|
5
|
+
# Raised when a specific API version is requested but doesn't exist in the treaty
|
|
6
|
+
#
|
|
7
|
+
# ## Purpose
|
|
8
|
+
#
|
|
9
|
+
# Prevents execution with non-existent API versions. Helps clients
|
|
10
|
+
# discover available versions and prevents errors from version mismatches.
|
|
11
|
+
# Ensures API versioning integrity.
|
|
12
|
+
#
|
|
13
|
+
# ## Usage
|
|
14
|
+
#
|
|
15
|
+
# Raised automatically during version resolution when a requested version
|
|
16
|
+
# is not defined in the treaty:
|
|
17
|
+
#
|
|
18
|
+
# ### Example: Requesting Non-Existent Version
|
|
19
|
+
# ```ruby
|
|
20
|
+
# class PostsTreaty < ApplicationTreaty
|
|
21
|
+
# version 1 do
|
|
22
|
+
# request { string :title }
|
|
23
|
+
# response(200) { object :post }
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
# version 2, default: true do
|
|
27
|
+
# request { string :title, :summary }
|
|
28
|
+
# response(200) { object :post }
|
|
29
|
+
# end
|
|
30
|
+
# end
|
|
31
|
+
#
|
|
32
|
+
# # Client requests version 3 (doesn't exist)
|
|
33
|
+
# PostsTreaty.call!(version: "3", params: { title: "Test" })
|
|
34
|
+
# # => Raises Treaty::Exceptions::VersionNotFound
|
|
35
|
+
# # => "Version 3 not found in treaty definition"
|
|
36
|
+
# ```
|
|
37
|
+
#
|
|
38
|
+
# ### Example: Version Format Mismatch
|
|
39
|
+
# ```ruby
|
|
40
|
+
# # Treaty defines version 1
|
|
41
|
+
# version 1 do
|
|
42
|
+
# # ...
|
|
43
|
+
# end
|
|
44
|
+
#
|
|
45
|
+
# # Client requests "1.0.0" (treated as different from "1")
|
|
46
|
+
# PostsTreaty.call!(version: "1.0.0", params: {})
|
|
47
|
+
# # => Raises Treaty::Exceptions::VersionNotFound if exact match not found
|
|
48
|
+
# ```
|
|
49
|
+
#
|
|
50
|
+
# ### Example: Typo in Version Number
|
|
51
|
+
# ```ruby
|
|
52
|
+
# PostsTreaty.call!(version: "v2", params: {}) # Should be "2"
|
|
53
|
+
# # => Raises Treaty::Exceptions::VersionNotFound
|
|
54
|
+
# ```
|
|
55
|
+
#
|
|
56
|
+
# ## Integration
|
|
57
|
+
#
|
|
58
|
+
# Can be rescued by application controllers to return appropriate HTTP status:
|
|
59
|
+
#
|
|
60
|
+
# ```ruby
|
|
61
|
+
# rescue_from Treaty::Exceptions::VersionNotFound, with: :render_version_not_found
|
|
62
|
+
#
|
|
63
|
+
# def render_version_not_found(exception)
|
|
64
|
+
# available_versions = extract_available_versions(exception)
|
|
65
|
+
#
|
|
66
|
+
# render json: {
|
|
67
|
+
# error: exception.message,
|
|
68
|
+
# available_versions: available_versions,
|
|
69
|
+
# hint: "Please use one of the available API versions"
|
|
70
|
+
# }, status: :not_found # HTTP 404
|
|
71
|
+
# end
|
|
72
|
+
# ```
|
|
73
|
+
#
|
|
74
|
+
# ## HTTP Status
|
|
75
|
+
#
|
|
76
|
+
# Typically returns HTTP 404 Not Found, indicating that the requested
|
|
77
|
+
# resource (API version) does not exist on the server.
|
|
78
|
+
#
|
|
79
|
+
# ## Common Scenarios
|
|
80
|
+
#
|
|
81
|
+
# ### 1. Client Using Outdated Version Number
|
|
82
|
+
# ```ruby
|
|
83
|
+
# # Version 1 was removed, only version 2 and 3 exist
|
|
84
|
+
# PostsTreaty.call!(version: "1", params: {})
|
|
85
|
+
# # => VersionNotFound
|
|
86
|
+
# ```
|
|
87
|
+
#
|
|
88
|
+
# ### 2. Client Using Future Version
|
|
89
|
+
# ```ruby
|
|
90
|
+
# # Client expects version 5 but only version 1-3 deployed
|
|
91
|
+
# PostsTreaty.call!(version: "5", params: {})
|
|
92
|
+
# # => VersionNotFound
|
|
93
|
+
# ```
|
|
94
|
+
#
|
|
95
|
+
# ### 3. Version Format Inconsistency
|
|
96
|
+
# ```ruby
|
|
97
|
+
# # Treaty uses integers, client uses semantic versioning
|
|
98
|
+
# version 1 do ... end
|
|
99
|
+
# version 2 do ... end
|
|
100
|
+
#
|
|
101
|
+
# PostsTreaty.call!(version: "v2.0.0", params: {})
|
|
102
|
+
# # => VersionNotFound (should use "2")
|
|
103
|
+
# ```
|
|
104
|
+
#
|
|
105
|
+
# ## Best Practices
|
|
106
|
+
#
|
|
107
|
+
# ### For API Providers
|
|
108
|
+
#
|
|
109
|
+
# 1. **Version numbering consistency**:
|
|
110
|
+
# ```ruby
|
|
111
|
+
# # Choose one format and stick with it
|
|
112
|
+
# version 1 do ... end
|
|
113
|
+
# version 2 do ... end
|
|
114
|
+
# # OR
|
|
115
|
+
# version "1.0.0" do ... end
|
|
116
|
+
# version "2.0.0" do ... end
|
|
117
|
+
# ```
|
|
118
|
+
#
|
|
119
|
+
# 2. **Document available versions** in API documentation
|
|
120
|
+
#
|
|
121
|
+
# 3. **Provide version discovery endpoint**:
|
|
122
|
+
# ```ruby
|
|
123
|
+
# GET /api/versions
|
|
124
|
+
# # => { available_versions: ["1", "2", "3"], default: "3" }
|
|
125
|
+
# ```
|
|
126
|
+
#
|
|
127
|
+
# 4. **Use deprecation** before removal:
|
|
128
|
+
# ```ruby
|
|
129
|
+
# version 1 do
|
|
130
|
+
# deprecated true # Warn before removing
|
|
131
|
+
# end
|
|
132
|
+
# ```
|
|
133
|
+
#
|
|
134
|
+
# ### For API Clients
|
|
135
|
+
#
|
|
136
|
+
# 1. **Validate version before requests**
|
|
137
|
+
# 2. **Handle version errors gracefully**
|
|
138
|
+
# 3. **Check API documentation** for available versions
|
|
139
|
+
# 4. **Implement version fallback logic** when appropriate
|
|
140
|
+
#
|
|
141
|
+
# ## Difference from SpecifiedVersionNotFound
|
|
142
|
+
#
|
|
143
|
+
# - **SpecifiedVersionNotFound**: No version specified (nil/blank), no default configured
|
|
144
|
+
# - **VersionNotFound**: Specific version specified but doesn't exist in treaty
|
|
145
|
+
#
|
|
146
|
+
# ## Difference from Deprecated
|
|
147
|
+
#
|
|
148
|
+
# - **VersionNotFound**: Version doesn't exist at all (HTTP 404)
|
|
149
|
+
# - **Deprecated**: Version exists but is marked as deprecated (HTTP 410)
|
|
150
|
+
#
|
|
151
|
+
# ## Version Resolution Order
|
|
152
|
+
#
|
|
153
|
+
# 1. Version specified → Look for exact match
|
|
154
|
+
# 2. Exact match not found → Raise VersionNotFound
|
|
155
|
+
# 3. Match found but deprecated → Raise Deprecated
|
|
156
|
+
# 4. Match found and active → Use version
|
|
157
|
+
class VersionNotFound < Base
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
data/lib/treaty/version.rb
CHANGED
|
@@ -7,15 +7,15 @@ module Treaty
|
|
|
7
7
|
new(...).resolve!
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
def initialize(
|
|
11
|
-
@
|
|
10
|
+
def initialize(specified_version:, collection_of_versions:)
|
|
11
|
+
@specified_version = specified_version
|
|
12
12
|
@collection_of_versions = collection_of_versions
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def resolve!
|
|
16
16
|
determined_factory =
|
|
17
|
-
if
|
|
18
|
-
default_version_factory ||
|
|
17
|
+
if specified_version_blank?
|
|
18
|
+
default_version_factory || raise_specified_version_not_found!
|
|
19
19
|
else
|
|
20
20
|
version_factory || raise_version_not_found!
|
|
21
21
|
end
|
|
@@ -30,7 +30,7 @@ module Treaty
|
|
|
30
30
|
def version_factory
|
|
31
31
|
@version_factory ||=
|
|
32
32
|
@collection_of_versions.find do |factory|
|
|
33
|
-
factory.version.version == @
|
|
33
|
+
factory.version.version == @specified_version
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
36
|
|
|
@@ -39,22 +39,22 @@ module Treaty
|
|
|
39
39
|
@collection_of_versions.find(&:default_result)
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
-
def
|
|
43
|
-
@
|
|
42
|
+
def specified_version_blank?
|
|
43
|
+
@specified_version.to_s.strip.empty?
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
##########################################################################
|
|
47
47
|
|
|
48
|
-
def
|
|
49
|
-
raise Treaty::Exceptions::
|
|
50
|
-
I18n.t("treaty.versioning.resolver.
|
|
48
|
+
def raise_specified_version_not_found!
|
|
49
|
+
raise Treaty::Exceptions::SpecifiedVersionNotFound,
|
|
50
|
+
I18n.t("treaty.versioning.resolver.specified_version_required")
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
def raise_version_not_found!
|
|
54
|
-
raise Treaty::Exceptions::
|
|
54
|
+
raise Treaty::Exceptions::VersionNotFound,
|
|
55
55
|
I18n.t(
|
|
56
56
|
"treaty.versioning.resolver.version_not_found",
|
|
57
|
-
version: @
|
|
57
|
+
version: @specified_version
|
|
58
58
|
)
|
|
59
59
|
end
|
|
60
60
|
|
|
@@ -62,7 +62,7 @@ module Treaty
|
|
|
62
62
|
raise Treaty::Exceptions::Deprecated,
|
|
63
63
|
I18n.t(
|
|
64
64
|
"treaty.versioning.resolver.version_deprecated",
|
|
65
|
-
version: @
|
|
65
|
+
version: @specified_version
|
|
66
66
|
)
|
|
67
67
|
end
|
|
68
68
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: treaty
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.10.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Anton Sokolov
|
|
@@ -186,9 +186,11 @@ files:
|
|
|
186
186
|
- lib/treaty/exceptions/method_name.rb
|
|
187
187
|
- lib/treaty/exceptions/nested_attributes.rb
|
|
188
188
|
- lib/treaty/exceptions/not_implemented.rb
|
|
189
|
+
- lib/treaty/exceptions/specified_version_not_found.rb
|
|
189
190
|
- lib/treaty/exceptions/strategy.rb
|
|
190
191
|
- lib/treaty/exceptions/unexpected.rb
|
|
191
192
|
- lib/treaty/exceptions/validation.rb
|
|
193
|
+
- lib/treaty/exceptions/version_not_found.rb
|
|
192
194
|
- lib/treaty/info/entity/builder.rb
|
|
193
195
|
- lib/treaty/info/entity/dsl.rb
|
|
194
196
|
- lib/treaty/info/entity/result.rb
|