treaty 0.9.0 → 0.11.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 +3 -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_default_deprecated_conflict.rb +83 -0
- data/lib/treaty/exceptions/version_multiple_defaults.rb +111 -0
- data/lib/treaty/exceptions/version_not_found.rb +160 -0
- data/lib/treaty/version.rb +1 -1
- data/lib/treaty/versions/dsl.rb +12 -0
- data/lib/treaty/versions/factory.rb +15 -0
- data/lib/treaty/versions/resolver.rb +13 -13
- data/lib/treaty/versions/workspace.rb +1 -1
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6ec3d8b8448c1cb6634404040ed78864beb81a317b705a14d6f3684caaaf5952
|
|
4
|
+
data.tar.gz: a21afccd9a25da7531ff561ed4baae2cc0011ed8c9c5b09b7ceb1370b79ba353
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0a8281e2551007b9c6b514b3d8d664d484b27e757733dd6a94d191bfdf8cce1d50a5b4f3bdee8fb8e4cadb51b1b90802697f0cd06a929c8cf39482f6aef6c5c3
|
|
7
|
+
data.tar.gz: 1855561545ee10307c8094377ed28a132a6a9ac8f9347801f9f1d80e7f6adbb6fddc812b34e2b7960336d8558d84e9d48691aeea525c6357962b66e2a0a56e7e
|
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
|
|
|
@@ -92,6 +92,8 @@ en:
|
|
|
92
92
|
factory:
|
|
93
93
|
invalid_default_option: "Default option for version must be true, false, or a Proc, got: %{type}"
|
|
94
94
|
unknown_method: "Unknown method '%{method}' in version definition. Available methods: summary, strategy, deprecated, request, response, delegate_to"
|
|
95
|
+
default_deprecated_conflict: "Version %{version} cannot be both default and deprecated. A default version must be active and usable. Either remove 'default: true' or remove the 'deprecated' declaration."
|
|
96
|
+
multiple_defaults: "Cannot have multiple versions marked as default. Only one version can be the default. Please review your treaty definition and ensure only one version has 'default: true'."
|
|
95
97
|
|
|
96
98
|
# Strategy validation
|
|
97
99
|
strategy:
|
|
@@ -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,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
module Exceptions
|
|
5
|
+
# Raised when a version is marked as both default and deprecated
|
|
6
|
+
#
|
|
7
|
+
# ## Purpose
|
|
8
|
+
#
|
|
9
|
+
# Prevents the logical contradiction of having a version that is both
|
|
10
|
+
# the default version (used when no version is specified) and deprecated
|
|
11
|
+
# (should not be used). A default version must be active and usable.
|
|
12
|
+
#
|
|
13
|
+
# ## Usage
|
|
14
|
+
#
|
|
15
|
+
# This exception is raised automatically during version definition validation:
|
|
16
|
+
#
|
|
17
|
+
# ### Invalid Configuration
|
|
18
|
+
# ```ruby
|
|
19
|
+
# class PostsTreaty < ApplicationTreaty
|
|
20
|
+
# version 1, default: true do
|
|
21
|
+
# deprecated true # ERROR: Cannot be both default and deprecated
|
|
22
|
+
# # ... rest of version definition
|
|
23
|
+
# end
|
|
24
|
+
# end
|
|
25
|
+
# ```
|
|
26
|
+
#
|
|
27
|
+
# ### Valid Configurations
|
|
28
|
+
# ```ruby
|
|
29
|
+
# # Option 1: Default version without deprecation
|
|
30
|
+
# version 1, default: true do
|
|
31
|
+
# # No deprecated call - valid
|
|
32
|
+
# end
|
|
33
|
+
#
|
|
34
|
+
# # Option 2: Deprecated version without default
|
|
35
|
+
# version 1 do
|
|
36
|
+
# deprecated true # Valid, but not default
|
|
37
|
+
# end
|
|
38
|
+
#
|
|
39
|
+
# # Option 3: Neither default nor deprecated
|
|
40
|
+
# version 1 do
|
|
41
|
+
# # Regular version - valid
|
|
42
|
+
# end
|
|
43
|
+
# ```
|
|
44
|
+
#
|
|
45
|
+
# ## When It's Raised
|
|
46
|
+
#
|
|
47
|
+
# The exception is raised during treaty class loading when:
|
|
48
|
+
# 1. A version has `default: true` in the version declaration
|
|
49
|
+
# 2. AND the same version calls `deprecated` method with any truthy value or block
|
|
50
|
+
#
|
|
51
|
+
# ## Integration
|
|
52
|
+
#
|
|
53
|
+
# This is a configuration error that should be caught during development.
|
|
54
|
+
# It can be rescued by application controllers:
|
|
55
|
+
#
|
|
56
|
+
# ```ruby
|
|
57
|
+
# rescue_from Treaty::Exceptions::VersionDefaultDeprecatedConflict, with: :render_config_error
|
|
58
|
+
#
|
|
59
|
+
# def render_config_error(exception)
|
|
60
|
+
# render json: { error: exception.message }, status: :internal_server_error # HTTP 500
|
|
61
|
+
# end
|
|
62
|
+
# ```
|
|
63
|
+
#
|
|
64
|
+
# ## HTTP Status
|
|
65
|
+
#
|
|
66
|
+
# Returns HTTP 500 Internal Server Error as this indicates a configuration
|
|
67
|
+
# problem in the application code that should be fixed by developers.
|
|
68
|
+
#
|
|
69
|
+
# ## Resolution
|
|
70
|
+
#
|
|
71
|
+
# To fix this error, choose one of the following:
|
|
72
|
+
# 1. Remove `default: true` if the version should be deprecated
|
|
73
|
+
# 2. Remove the `deprecated` call if the version should be default
|
|
74
|
+
# 3. Create a new version that will be the default, and deprecate the old one
|
|
75
|
+
#
|
|
76
|
+
# ## Related Exceptions
|
|
77
|
+
#
|
|
78
|
+
# - `VersionMultipleDefaults` - When multiple versions are marked as default
|
|
79
|
+
# - `Deprecated` - When attempting to use an already deprecated version
|
|
80
|
+
class VersionDefaultDeprecatedConflict < Base
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
module Exceptions
|
|
5
|
+
# Raised when multiple versions are marked as default in the same treaty
|
|
6
|
+
#
|
|
7
|
+
# ## Purpose
|
|
8
|
+
#
|
|
9
|
+
# Ensures that only one version in a treaty can be marked as the default.
|
|
10
|
+
# The default version is used when clients don't specify an API version
|
|
11
|
+
# in their requests, so having multiple defaults would create ambiguity.
|
|
12
|
+
#
|
|
13
|
+
# ## Usage
|
|
14
|
+
#
|
|
15
|
+
# This exception is raised automatically during version definition validation:
|
|
16
|
+
#
|
|
17
|
+
# ### Invalid Configuration
|
|
18
|
+
# ```ruby
|
|
19
|
+
# class PostsTreaty < ApplicationTreaty
|
|
20
|
+
# version 1, default: true do
|
|
21
|
+
# # First default version
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# version 2, default: true do
|
|
25
|
+
# # ERROR: Cannot have two default versions
|
|
26
|
+
# end
|
|
27
|
+
# end
|
|
28
|
+
# ```
|
|
29
|
+
#
|
|
30
|
+
# ### Valid Configurations
|
|
31
|
+
# ```ruby
|
|
32
|
+
# # Option 1: Single default version
|
|
33
|
+
# class PostsTreaty < ApplicationTreaty
|
|
34
|
+
# version 1 do
|
|
35
|
+
# # Not default
|
|
36
|
+
# end
|
|
37
|
+
#
|
|
38
|
+
# version 2, default: true do
|
|
39
|
+
# # Only one default - valid
|
|
40
|
+
# end
|
|
41
|
+
# end
|
|
42
|
+
#
|
|
43
|
+
# # Option 2: No default version (version must be explicitly specified)
|
|
44
|
+
# class PostsTreaty < ApplicationTreaty
|
|
45
|
+
# version 1 do
|
|
46
|
+
# # Not default
|
|
47
|
+
# end
|
|
48
|
+
#
|
|
49
|
+
# version 2 do
|
|
50
|
+
# # Not default - valid, but version must be specified in requests
|
|
51
|
+
# end
|
|
52
|
+
# end
|
|
53
|
+
# ```
|
|
54
|
+
#
|
|
55
|
+
# ## When It's Raised
|
|
56
|
+
#
|
|
57
|
+
# The exception is raised during treaty class loading when:
|
|
58
|
+
# 1. A second version is being defined with `default: true`
|
|
59
|
+
# 2. AND another version already has `default: true`
|
|
60
|
+
#
|
|
61
|
+
# ## Integration
|
|
62
|
+
#
|
|
63
|
+
# This is a configuration error that should be caught during development.
|
|
64
|
+
# It can be rescued by application controllers:
|
|
65
|
+
#
|
|
66
|
+
# ```ruby
|
|
67
|
+
# rescue_from Treaty::Exceptions::VersionMultipleDefaults, with: :render_config_error
|
|
68
|
+
#
|
|
69
|
+
# def render_config_error(exception)
|
|
70
|
+
# render json: { error: exception.message }, status: :internal_server_error # HTTP 500
|
|
71
|
+
# end
|
|
72
|
+
# ```
|
|
73
|
+
#
|
|
74
|
+
# ## HTTP Status
|
|
75
|
+
#
|
|
76
|
+
# Returns HTTP 500 Internal Server Error as this indicates a configuration
|
|
77
|
+
# problem in the application code that should be fixed by developers.
|
|
78
|
+
#
|
|
79
|
+
# ## Resolution
|
|
80
|
+
#
|
|
81
|
+
# To fix this error:
|
|
82
|
+
# 1. Identify which version should truly be the default
|
|
83
|
+
# 2. Remove `default: true` from all other versions
|
|
84
|
+
# 3. Keep only one `default: true` declaration
|
|
85
|
+
#
|
|
86
|
+
# ## Best Practice
|
|
87
|
+
#
|
|
88
|
+
# Typically, the newest stable version should be marked as default:
|
|
89
|
+
#
|
|
90
|
+
# ```ruby
|
|
91
|
+
# version 1 do
|
|
92
|
+
# deprecated true # Old version
|
|
93
|
+
# end
|
|
94
|
+
#
|
|
95
|
+
# version 2 do
|
|
96
|
+
# # Stable version
|
|
97
|
+
# end
|
|
98
|
+
#
|
|
99
|
+
# version 3, default: true do
|
|
100
|
+
# # Latest stable version - the default
|
|
101
|
+
# end
|
|
102
|
+
# ```
|
|
103
|
+
#
|
|
104
|
+
# ## Related Exceptions
|
|
105
|
+
#
|
|
106
|
+
# - `VersionDefaultDeprecatedConflict` - When a single version has both default and deprecated
|
|
107
|
+
# - `SpecifiedVersionNotFound` - When no default exists and no version is specified
|
|
108
|
+
class VersionMultipleDefaults < Base
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
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
data/lib/treaty/versions/dsl.rb
CHANGED
|
@@ -15,6 +15,9 @@ module Treaty
|
|
|
15
15
|
@version_factory = Factory.new(version:, default:)
|
|
16
16
|
|
|
17
17
|
@version_factory.instance_eval(&block)
|
|
18
|
+
@version_factory.validate_after_block!
|
|
19
|
+
|
|
20
|
+
validate_multiple_defaults! if @version_factory.default_result == true
|
|
18
21
|
|
|
19
22
|
collection_of_versions << @version_factory
|
|
20
23
|
|
|
@@ -24,6 +27,15 @@ module Treaty
|
|
|
24
27
|
def collection_of_versions
|
|
25
28
|
@collection_of_versions ||= Collection.new
|
|
26
29
|
end
|
|
30
|
+
|
|
31
|
+
def validate_multiple_defaults!
|
|
32
|
+
existing_defaults = collection_of_versions.map(&:default_result).count(true)
|
|
33
|
+
|
|
34
|
+
return if existing_defaults.zero?
|
|
35
|
+
|
|
36
|
+
raise Treaty::Exceptions::VersionMultipleDefaults,
|
|
37
|
+
I18n.t("treaty.versioning.factory.multiple_defaults")
|
|
38
|
+
end
|
|
27
39
|
end
|
|
28
40
|
end
|
|
29
41
|
end
|
|
@@ -27,6 +27,10 @@ module Treaty
|
|
|
27
27
|
validate_default_option!
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
+
def validate_after_block!
|
|
31
|
+
validate_default_deprecated_conflict!
|
|
32
|
+
end
|
|
33
|
+
|
|
30
34
|
def summary(text)
|
|
31
35
|
@summary_text = text
|
|
32
36
|
end
|
|
@@ -88,6 +92,17 @@ module Treaty
|
|
|
88
92
|
)
|
|
89
93
|
end
|
|
90
94
|
|
|
95
|
+
def validate_default_deprecated_conflict!
|
|
96
|
+
return unless @default_result == true
|
|
97
|
+
return unless @deprecated_result == true
|
|
98
|
+
|
|
99
|
+
raise Treaty::Exceptions::VersionDefaultDeprecatedConflict,
|
|
100
|
+
I18n.t(
|
|
101
|
+
"treaty.versioning.factory.default_deprecated_conflict",
|
|
102
|
+
version: @version.version
|
|
103
|
+
)
|
|
104
|
+
end
|
|
105
|
+
|
|
91
106
|
##########################################################################
|
|
92
107
|
|
|
93
108
|
def method_missing(name, *, &_block)
|
|
@@ -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.11.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Anton Sokolov
|
|
@@ -186,9 +186,13 @@ 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_default_deprecated_conflict.rb
|
|
194
|
+
- lib/treaty/exceptions/version_multiple_defaults.rb
|
|
195
|
+
- lib/treaty/exceptions/version_not_found.rb
|
|
192
196
|
- lib/treaty/info/entity/builder.rb
|
|
193
197
|
- lib/treaty/info/entity/dsl.rb
|
|
194
198
|
- lib/treaty/info/entity/result.rb
|