treaty 0.1.0 → 0.3.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 +104 -16
- data/config/locales/en.yml +97 -0
- data/lib/treaty/attribute/base.rb +10 -8
- data/lib/treaty/attribute/builder/base.rb +3 -2
- data/lib/treaty/attribute/option/base.rb +1 -0
- data/lib/treaty/attribute/option/modifiers/as_modifier.rb +3 -2
- data/lib/treaty/attribute/option/validators/inclusion_validator.rb +6 -6
- data/lib/treaty/attribute/option/validators/required_validator.rb +2 -4
- data/lib/treaty/attribute/option/validators/type_validator.rb +39 -15
- data/lib/treaty/attribute/option_normalizer.rb +2 -1
- data/lib/treaty/attribute/option_orchestrator.rb +4 -3
- data/lib/treaty/attribute/validation/base.rb +2 -3
- data/lib/treaty/attribute/validation/nested_array_validator.rb +12 -7
- data/lib/treaty/attribute/validation/nested_transformer.rb +13 -7
- data/lib/treaty/attribute/validation/orchestrator/base.rb +2 -4
- data/lib/treaty/controller/dsl.rb +2 -2
- data/lib/treaty/exceptions/base.rb +39 -0
- data/lib/treaty/exceptions/class_name.rb +39 -0
- data/lib/treaty/exceptions/deprecated.rb +46 -0
- data/lib/treaty/exceptions/execution.rb +58 -0
- data/lib/treaty/exceptions/method_name.rb +47 -0
- data/lib/treaty/exceptions/nested_attributes.rb +57 -0
- data/lib/treaty/exceptions/not_implemented.rb +32 -0
- data/lib/treaty/exceptions/strategy.rb +55 -0
- data/lib/treaty/exceptions/unexpected.rb +62 -0
- data/lib/treaty/exceptions/validation.rb +89 -0
- data/lib/treaty/info/builder.rb +3 -3
- data/lib/treaty/request/attribute/attribute.rb +1 -1
- data/lib/treaty/response/attribute/attribute.rb +1 -1
- data/lib/treaty/strategy.rb +2 -2
- data/lib/treaty/version.rb +1 -1
- data/lib/treaty/versions/execution/request.rb +24 -28
- data/lib/treaty/versions/factory.rb +3 -4
- data/lib/treaty/versions/resolver.rb +3 -6
- metadata +21 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 34516da12bec1bd3df351363dacd13fc3e7e9c31806fef4a6aecf37be88754a8
|
|
4
|
+
data.tar.gz: 71bb387ce08aa6a3d42de04bef6e2c7e281089184e0815ad2679a9b87df30213
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '0398e7fa2f958bb24331f239cedd5db66880e2e08f402b82659b63a99941176e42ff64c867bdcd5c2aae054e4b35410f132d44f37724046c14b5c6967e9c6e6a'
|
|
7
|
+
data.tar.gz: 25433a8f90b4e6ee4528e3fa933be6ad7a6443ad593b12c99b9cc2fb3a110723b92690a2bbb2843e7de14fc27f5e7b806008fd361c31bbd4c74bd2a906e18b7f
|
data/README.md
CHANGED
|
@@ -1,32 +1,120 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
<h1>Treaty</h1>
|
|
3
|
+
<p>A Ruby library for defining and managing REST API contracts with versioning support.</p>
|
|
4
|
+
</div>
|
|
2
5
|
|
|
3
|
-
>
|
|
4
|
-
> This project is currently under development.
|
|
6
|
+
<div align="center">
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
[](https://rubygems.org/gems/treaty)
|
|
9
|
+
[](https://github.com/servactory/servactory/releases)
|
|
10
|
+
[](https://rubygems.org/gems/treaty)
|
|
11
|
+

|
|
12
|
+
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
## 📚 Documentation
|
|
16
|
+
|
|
17
|
+
Explore comprehensive guides and documentation at [docs](./docs):
|
|
18
|
+
|
|
19
|
+
- [Getting Started](./docs/getting-started.md) - installation and configuration
|
|
20
|
+
- [Core Concepts](./docs/core-concepts.md) - understand fundamental concepts
|
|
21
|
+
- [API Reference](./docs/api-reference.md) - complete API documentation
|
|
22
|
+
- [Examples](./docs/examples.md) - practical real-world examples
|
|
23
|
+
- [Internationalization](./docs/internationalization.md) - I18n and multilingual support
|
|
24
|
+
- [Full Documentation Index](./docs/README.md) - all documentation topics
|
|
25
|
+
|
|
26
|
+
## 💡 Why Treaty?
|
|
27
|
+
|
|
28
|
+
Treaty provides a complete solution for building versioned APIs in Ruby on Rails:
|
|
29
|
+
|
|
30
|
+
- **Type Safety** - Enforce strict type checking for request and response data
|
|
31
|
+
- **API Versioning** - Manage multiple concurrent API versions effortlessly
|
|
32
|
+
- **Built-in Validation** - Validate incoming requests and outgoing responses automatically
|
|
33
|
+
- **Data Transformation** - Transform data seamlessly between different API versions
|
|
34
|
+
- **Deprecation Management** - Mark versions as deprecated with flexible conditions
|
|
35
|
+
- **Internationalization** - Full I18n support for multilingual error messages
|
|
36
|
+
- **Well-documented** - Comprehensive guides and examples for every feature
|
|
37
|
+
|
|
38
|
+
## 🚀 Quick Start
|
|
7
39
|
|
|
8
40
|
### Installation
|
|
9
41
|
|
|
42
|
+
Add Treaty to your Gemfile:
|
|
43
|
+
|
|
10
44
|
```ruby
|
|
11
45
|
gem "treaty"
|
|
12
46
|
```
|
|
13
47
|
|
|
14
|
-
|
|
48
|
+
Run:
|
|
15
49
|
|
|
16
|
-
|
|
50
|
+
```bash
|
|
51
|
+
bundle install
|
|
52
|
+
```
|
|
17
53
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
54
|
+
### Define Treaty
|
|
55
|
+
|
|
56
|
+
Create your first API contract in `app/treaties/posts/create_treaty.rb`:
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
module Posts
|
|
60
|
+
class CreateTreaty < ApplicationTreaty
|
|
61
|
+
version 1, default: true do
|
|
62
|
+
strategy Treaty::Strategy::ADAPTER
|
|
63
|
+
|
|
64
|
+
request do
|
|
65
|
+
scope :post do
|
|
66
|
+
string :title, :required
|
|
67
|
+
string :content, :required
|
|
68
|
+
string :summary, :optional
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
response 201 do
|
|
73
|
+
scope :post do
|
|
74
|
+
string :id
|
|
75
|
+
string :title
|
|
76
|
+
string :content
|
|
77
|
+
string :summary
|
|
78
|
+
datetime :created_at
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
delegate_to Posts::CreateService
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Use in Controller
|
|
89
|
+
|
|
90
|
+
Define the treaty in your controller `app/controllers/posts_controller.rb`:
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
class PostsController < ApplicationController
|
|
94
|
+
# Treaty automatically:
|
|
95
|
+
# 1. Validates incoming parameters according to request definition
|
|
96
|
+
# 2. Calls Posts::CreateService with validated data
|
|
97
|
+
# 3. Validates service response according to response definition
|
|
98
|
+
# 4. Returns transformed data to client
|
|
99
|
+
treaty :create
|
|
100
|
+
end
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## 🤝 Contributing
|
|
104
|
+
|
|
105
|
+
We welcome contributions! You can help by:
|
|
106
|
+
|
|
107
|
+
- Reporting bugs and suggesting features
|
|
108
|
+
- Writing code and improving documentation
|
|
109
|
+
- Reviewing pull requests
|
|
110
|
+
- Sharing your experience with Treaty
|
|
111
|
+
|
|
112
|
+
Please read our [Contributing Guide](./CONTRIBUTING.md) before submitting a pull request.
|
|
23
113
|
|
|
24
|
-
##
|
|
114
|
+
## 🙏 Acknowledgments
|
|
25
115
|
|
|
26
|
-
|
|
27
|
-
Contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
|
28
|
-
We recommend reading the [contributing guide](./CONTRIBUTING.md) as well.
|
|
116
|
+
Thank you to all [contributors](https://github.com/servactory/treaty/graphs/contributors) who have helped make Treaty better!
|
|
29
117
|
|
|
30
|
-
## License
|
|
118
|
+
## 📄 License
|
|
31
119
|
|
|
32
120
|
Treaty is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
en:
|
|
2
|
+
treaty:
|
|
3
|
+
# ============================================================================
|
|
4
|
+
# Attributes: Definition, validation, and processing
|
|
5
|
+
# ============================================================================
|
|
6
|
+
attributes:
|
|
7
|
+
# Attribute value validators
|
|
8
|
+
validators:
|
|
9
|
+
required:
|
|
10
|
+
blank: "Attribute '%{attribute}' is required but was not provided or is empty"
|
|
11
|
+
|
|
12
|
+
type:
|
|
13
|
+
unknown_type: "Unknown type '%{type}' for attribute '%{attribute}'. Allowed types: %{allowed}"
|
|
14
|
+
mismatch:
|
|
15
|
+
integer: "Attribute '%{attribute}' must be an Integer, got %{actual}"
|
|
16
|
+
string: "Attribute '%{attribute}' must be a String, got %{actual}"
|
|
17
|
+
boolean: "Attribute '%{attribute}' must be a Boolean (true or false), got %{actual}"
|
|
18
|
+
object: "Attribute '%{attribute}' must be a Hash (object), got %{actual}"
|
|
19
|
+
array: "Attribute '%{attribute}' must be an Array, got %{actual}"
|
|
20
|
+
datetime: "Attribute '%{attribute}' must be a DateTime/Time/Date, got %{actual}"
|
|
21
|
+
|
|
22
|
+
inclusion:
|
|
23
|
+
invalid_schema: "Option 'inclusion' for attribute '%{attribute}' must have a non-empty array of allowed values"
|
|
24
|
+
not_included: "Attribute '%{attribute}' must be one of: %{allowed}. Got: '%{value}'"
|
|
25
|
+
|
|
26
|
+
# Nested structures validation
|
|
27
|
+
nested:
|
|
28
|
+
# Orchestrator errors
|
|
29
|
+
orchestrator:
|
|
30
|
+
collection_not_implemented: "Subclass must implement the collection_of_scopes method"
|
|
31
|
+
scope_data_not_implemented: "Subclass must implement the scope_data_for method"
|
|
32
|
+
|
|
33
|
+
# Array validation errors
|
|
34
|
+
array:
|
|
35
|
+
element_validation_error: "Error in array '%{attribute}' at index %{index}: Element must match one of the defined types. Errors: %{errors}"
|
|
36
|
+
element_type_error: "Error in array '%{attribute}' at index %{index}: Expected Hash but got %{actual}"
|
|
37
|
+
attribute_error: "Error in array '%{attribute}' at index %{index}: %{message}"
|
|
38
|
+
|
|
39
|
+
# Attribute options
|
|
40
|
+
options:
|
|
41
|
+
unknown: "Unknown options for attribute '%{attribute}': %{unknown}. Known options: %{known}"
|
|
42
|
+
|
|
43
|
+
# Attribute modifiers
|
|
44
|
+
modifiers:
|
|
45
|
+
as:
|
|
46
|
+
invalid_type: "Option 'as' for attribute '%{attribute}' must be a Symbol. Got: %{type}"
|
|
47
|
+
|
|
48
|
+
# Attribute builder DSL
|
|
49
|
+
builder:
|
|
50
|
+
not_implemented: "%{class} must implement #create_attribute"
|
|
51
|
+
|
|
52
|
+
# Attribute-level errors
|
|
53
|
+
errors:
|
|
54
|
+
nesting_level_exceeded: "Nesting level %{level} exceeds maximum allowed level of %{max_level}"
|
|
55
|
+
apply_defaults_not_implemented: "%{class} must implement #apply_defaults!"
|
|
56
|
+
process_nested_not_implemented: "%{class} must implement #process_nested_attributes"
|
|
57
|
+
|
|
58
|
+
# ============================================================================
|
|
59
|
+
# Versioning: API version management and resolution
|
|
60
|
+
# ============================================================================
|
|
61
|
+
versioning:
|
|
62
|
+
# Version resolver
|
|
63
|
+
resolver:
|
|
64
|
+
current_version_required: "Current version is required for validation"
|
|
65
|
+
version_not_found: "Version %{version} not found in treaty definition"
|
|
66
|
+
version_deprecated: "Version %{version} is deprecated and cannot be used"
|
|
67
|
+
|
|
68
|
+
# Version factory
|
|
69
|
+
factory:
|
|
70
|
+
invalid_default_option: "Default option for version must be true, false, or a Proc, got: %{type}"
|
|
71
|
+
unknown_method: "Unknown method: %{method}"
|
|
72
|
+
|
|
73
|
+
# Strategy validation
|
|
74
|
+
strategy:
|
|
75
|
+
unknown: "Unknown strategy: %{strategy}"
|
|
76
|
+
|
|
77
|
+
# ============================================================================
|
|
78
|
+
# Execution: Service and executor invocation
|
|
79
|
+
# ============================================================================
|
|
80
|
+
execution:
|
|
81
|
+
executor_missing: "Executor is not defined for version %{version}"
|
|
82
|
+
executor_empty: "Executor cannot be an empty string"
|
|
83
|
+
executor_not_found: "Executor class `%{class_name}` not found"
|
|
84
|
+
executor_invalid_type: "Invalid executor type: %{type}. Expected Proc, Class, String, or Symbol"
|
|
85
|
+
method_not_found: "Method '%{method}' not found in class '%{class_name}'"
|
|
86
|
+
proc_error: "%{message}"
|
|
87
|
+
servactory_input_error: "%{message}"
|
|
88
|
+
servactory_internal_error: "%{message}"
|
|
89
|
+
servactory_output_error: "%{message}"
|
|
90
|
+
servactory_failure_error: "%{message}"
|
|
91
|
+
regular_service_error: "%{message}"
|
|
92
|
+
|
|
93
|
+
# ============================================================================
|
|
94
|
+
# Controller DSL: Rails controller integration
|
|
95
|
+
# ============================================================================
|
|
96
|
+
controller:
|
|
97
|
+
treaty_class_not_found: "%{class_name}"
|
|
@@ -121,10 +121,10 @@ module Treaty
|
|
|
121
121
|
def validate_nesting_level!
|
|
122
122
|
return unless @nesting_level > Treaty::Engine.config.treaty.attribute_nesting_level
|
|
123
123
|
|
|
124
|
-
# TODO: It is necessary to implement a translation system (I18n).
|
|
125
124
|
raise Treaty::Exceptions::NestedAttributes,
|
|
126
|
-
"
|
|
127
|
-
|
|
125
|
+
I18n.t("treaty.attributes.errors.nesting_level_exceeded",
|
|
126
|
+
level: @nesting_level,
|
|
127
|
+
max_level: Treaty::Engine.config.treaty.attribute_nesting_level)
|
|
128
128
|
end
|
|
129
129
|
|
|
130
130
|
# Extracts helper symbols from arguments
|
|
@@ -150,22 +150,24 @@ module Treaty
|
|
|
150
150
|
# Applies default values for options based on context (request/response)
|
|
151
151
|
# Must be implemented in subclasses
|
|
152
152
|
#
|
|
153
|
-
# @raise [
|
|
153
|
+
# @raise [Treaty::Exceptions::NotImplemented] If subclass doesn't implement
|
|
154
154
|
# @return [void]
|
|
155
155
|
def apply_defaults!
|
|
156
156
|
# Must be implemented in subclasses
|
|
157
|
-
raise
|
|
157
|
+
raise Treaty::Exceptions::NotImplemented,
|
|
158
|
+
I18n.t("treaty.attributes.errors.apply_defaults_not_implemented", class: self.class)
|
|
158
159
|
end
|
|
159
160
|
|
|
160
161
|
# Processes nested attributes block for object/array types
|
|
161
162
|
# Must be implemented in subclasses
|
|
162
163
|
#
|
|
163
164
|
# @param block [Proc] Block containing nested attribute definitions
|
|
164
|
-
# @raise [
|
|
165
|
+
# @raise [Treaty::Exceptions::NotImplemented] If subclass doesn't implement
|
|
165
166
|
# @return [void]
|
|
166
|
-
def process_nested_attributes
|
|
167
|
+
def process_nested_attributes
|
|
167
168
|
# Must be implemented in subclasses
|
|
168
|
-
raise
|
|
169
|
+
raise Treaty::Exceptions::NotImplemented,
|
|
170
|
+
I18n.t("treaty.attributes.errors.process_nested_not_implemented", class: self.class)
|
|
169
171
|
end
|
|
170
172
|
end
|
|
171
173
|
end
|
|
@@ -130,11 +130,12 @@ module Treaty
|
|
|
130
130
|
|
|
131
131
|
# Creates an attribute instance (must be implemented in subclasses)
|
|
132
132
|
#
|
|
133
|
-
# @raise [
|
|
133
|
+
# @raise [Treaty::Exceptions::NotImplemented] If subclass doesn't implement
|
|
134
134
|
# @return [Attribute::Base] Created attribute instance
|
|
135
135
|
def create_attribute(*)
|
|
136
136
|
# Must be implemented in subclasses
|
|
137
|
-
raise
|
|
137
|
+
raise Treaty::Exceptions::NotImplemented,
|
|
138
|
+
I18n.t("treaty.attributes.builder.not_implemented", class: self.class)
|
|
138
139
|
end
|
|
139
140
|
end
|
|
140
141
|
end
|
|
@@ -132,6 +132,7 @@ module Treaty
|
|
|
132
132
|
end
|
|
133
133
|
|
|
134
134
|
# Gets custom error message from advanced mode schema
|
|
135
|
+
# Returns nil if no custom message, which triggers I18n default message
|
|
135
136
|
#
|
|
136
137
|
# @return [String, nil] Custom error message or nil for default message
|
|
137
138
|
def custom_message
|
|
@@ -53,9 +53,10 @@ module Treaty
|
|
|
53
53
|
|
|
54
54
|
return if target.is_a?(Symbol)
|
|
55
55
|
|
|
56
|
-
# TODO: It is necessary to implement a translation system (I18n).
|
|
57
56
|
raise Treaty::Exceptions::Validation,
|
|
58
|
-
"
|
|
57
|
+
I18n.t("treaty.attributes.modifiers.as.invalid_type",
|
|
58
|
+
attribute: @attribute_name,
|
|
59
|
+
type: target.class)
|
|
59
60
|
end
|
|
60
61
|
|
|
61
62
|
# Indicates that AsModifier transforms attribute names
|
|
@@ -28,9 +28,8 @@ module Treaty
|
|
|
28
28
|
|
|
29
29
|
return if allowed_values.is_a?(Array) && !allowed_values.empty?
|
|
30
30
|
|
|
31
|
-
# TODO: It is necessary to implement a translation system (I18n).
|
|
32
31
|
raise Treaty::Exceptions::Validation,
|
|
33
|
-
"
|
|
32
|
+
I18n.t("treaty.attributes.validators.inclusion.invalid_schema", attribute: @attribute_name)
|
|
34
33
|
end
|
|
35
34
|
|
|
36
35
|
# Validates that value is included in allowed set
|
|
@@ -48,7 +47,6 @@ module Treaty
|
|
|
48
47
|
|
|
49
48
|
message = custom_message || default_message(allowed_values, value)
|
|
50
49
|
|
|
51
|
-
# TODO: It is necessary to implement a translation system (I18n).
|
|
52
50
|
raise Treaty::Exceptions::Validation, message
|
|
53
51
|
end
|
|
54
52
|
|
|
@@ -64,14 +62,16 @@ module Treaty
|
|
|
64
62
|
|
|
65
63
|
private
|
|
66
64
|
|
|
67
|
-
# Generates default error message with allowed values
|
|
65
|
+
# Generates default error message with allowed values using I18n
|
|
68
66
|
#
|
|
69
67
|
# @param allowed_values [Array] Array of allowed values
|
|
70
68
|
# @param value [Object] The actual value that failed validation
|
|
71
69
|
# @return [String] Default error message
|
|
72
70
|
def default_message(allowed_values, value)
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
I18n.t("treaty.attributes.validators.inclusion.not_included",
|
|
72
|
+
attribute: @attribute_name,
|
|
73
|
+
allowed: allowed_values.join(", "),
|
|
74
|
+
value:)
|
|
75
75
|
end
|
|
76
76
|
end
|
|
77
77
|
end
|
|
@@ -53,7 +53,6 @@ module Treaty
|
|
|
53
53
|
|
|
54
54
|
message = custom_message || default_message
|
|
55
55
|
|
|
56
|
-
# TODO: It is necessary to implement a translation system (I18n).
|
|
57
56
|
raise Treaty::Exceptions::Validation, message
|
|
58
57
|
end
|
|
59
58
|
|
|
@@ -80,12 +79,11 @@ module Treaty
|
|
|
80
79
|
true
|
|
81
80
|
end
|
|
82
81
|
|
|
83
|
-
# Generates default error message
|
|
82
|
+
# Generates default error message using I18n
|
|
84
83
|
#
|
|
85
84
|
# @return [String] Default error message
|
|
86
85
|
def default_message
|
|
87
|
-
|
|
88
|
-
"Attribute '#{@attribute_name}' is required but was not provided or is empty"
|
|
86
|
+
I18n.t("treaty.attributes.validators.required.blank", attribute: @attribute_name)
|
|
89
87
|
end
|
|
90
88
|
end
|
|
91
89
|
end
|
|
@@ -10,6 +10,7 @@ module Treaty
|
|
|
10
10
|
#
|
|
11
11
|
# - `:integer` - Ruby Integer
|
|
12
12
|
# - `:string` - Ruby String
|
|
13
|
+
# - `:boolean` - Ruby TrueClass or FalseClass
|
|
13
14
|
# - `:object` - Ruby Hash (for nested objects)
|
|
14
15
|
# - `:array` - Ruby Array (for collections)
|
|
15
16
|
# - `:datetime` - Ruby DateTime, Time, or Date
|
|
@@ -19,6 +20,7 @@ module Treaty
|
|
|
19
20
|
# Simple types:
|
|
20
21
|
# integer :age
|
|
21
22
|
# string :name
|
|
23
|
+
# boolean :published
|
|
22
24
|
# datetime :created_at
|
|
23
25
|
#
|
|
24
26
|
# Nested structures:
|
|
@@ -41,7 +43,7 @@ module Treaty
|
|
|
41
43
|
# TypeValidator doesn't use option_schema - it validates based on attribute_type.
|
|
42
44
|
# This validator is always active for all attributes.
|
|
43
45
|
class TypeValidator < Treaty::Attribute::Option::Base
|
|
44
|
-
ALLOWED_TYPES = %i[integer string object array datetime].freeze
|
|
46
|
+
ALLOWED_TYPES = %i[integer string boolean object array datetime].freeze
|
|
45
47
|
|
|
46
48
|
# Validates that the attribute type is one of the allowed types
|
|
47
49
|
#
|
|
@@ -50,10 +52,11 @@ module Treaty
|
|
|
50
52
|
def validate_schema!
|
|
51
53
|
return if ALLOWED_TYPES.include?(@attribute_type)
|
|
52
54
|
|
|
53
|
-
# TODO: It is necessary to implement a translation system (I18n).
|
|
54
55
|
raise Treaty::Exceptions::Validation,
|
|
55
|
-
"
|
|
56
|
-
|
|
56
|
+
I18n.t("treaty.attributes.validators.type.unknown_type",
|
|
57
|
+
type: @attribute_type,
|
|
58
|
+
attribute: @attribute_name,
|
|
59
|
+
allowed: ALLOWED_TYPES.join(", "))
|
|
57
60
|
end
|
|
58
61
|
|
|
59
62
|
# Validates that the value matches the declared type
|
|
@@ -62,7 +65,7 @@ module Treaty
|
|
|
62
65
|
# @param value [Object] The value to validate
|
|
63
66
|
# @raise [Treaty::Exceptions::Validation] If value type doesn't match
|
|
64
67
|
# @return [void]
|
|
65
|
-
def validate_value!(value) # rubocop:disable Metrics/MethodLength
|
|
68
|
+
def validate_value!(value) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
|
|
66
69
|
return if value.nil? # Type validation doesn't check for nil, required does.
|
|
67
70
|
|
|
68
71
|
case @attribute_type
|
|
@@ -70,6 +73,8 @@ module Treaty
|
|
|
70
73
|
validate_integer!(value)
|
|
71
74
|
when :string
|
|
72
75
|
validate_string!(value)
|
|
76
|
+
when :boolean
|
|
77
|
+
validate_boolean!(value)
|
|
73
78
|
when :object
|
|
74
79
|
validate_object!(value)
|
|
75
80
|
when :array
|
|
@@ -89,9 +94,10 @@ module Treaty
|
|
|
89
94
|
def validate_integer!(value)
|
|
90
95
|
return if value.is_a?(Integer)
|
|
91
96
|
|
|
92
|
-
# TODO: It is necessary to implement a translation system (I18n).
|
|
93
97
|
raise Treaty::Exceptions::Validation,
|
|
94
|
-
"
|
|
98
|
+
I18n.t("treaty.attributes.validators.type.mismatch.integer",
|
|
99
|
+
attribute: @attribute_name,
|
|
100
|
+
actual: value.class)
|
|
95
101
|
end
|
|
96
102
|
|
|
97
103
|
# Validates that value is a String
|
|
@@ -102,9 +108,24 @@ module Treaty
|
|
|
102
108
|
def validate_string!(value)
|
|
103
109
|
return if value.is_a?(String)
|
|
104
110
|
|
|
105
|
-
# TODO: It is necessary to implement a translation system (I18n).
|
|
106
111
|
raise Treaty::Exceptions::Validation,
|
|
107
|
-
"
|
|
112
|
+
I18n.t("treaty.attributes.validators.type.mismatch.string",
|
|
113
|
+
attribute: @attribute_name,
|
|
114
|
+
actual: value.class)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Validates that value is a Boolean (TrueClass or FalseClass)
|
|
118
|
+
#
|
|
119
|
+
# @param value [Object] The value to validate
|
|
120
|
+
# @raise [Treaty::Exceptions::Validation] If value is not a Boolean
|
|
121
|
+
# @return [void]
|
|
122
|
+
def validate_boolean!(value)
|
|
123
|
+
return if value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
|
124
|
+
|
|
125
|
+
raise Treaty::Exceptions::Validation,
|
|
126
|
+
I18n.t("treaty.attributes.validators.type.mismatch.boolean",
|
|
127
|
+
attribute: @attribute_name,
|
|
128
|
+
actual: value.class)
|
|
108
129
|
end
|
|
109
130
|
|
|
110
131
|
# Validates that value is a Hash (object type)
|
|
@@ -115,9 +136,10 @@ module Treaty
|
|
|
115
136
|
def validate_object!(value)
|
|
116
137
|
return if value.is_a?(Hash)
|
|
117
138
|
|
|
118
|
-
# TODO: It is necessary to implement a translation system (I18n).
|
|
119
139
|
raise Treaty::Exceptions::Validation,
|
|
120
|
-
"
|
|
140
|
+
I18n.t("treaty.attributes.validators.type.mismatch.object",
|
|
141
|
+
attribute: @attribute_name,
|
|
142
|
+
actual: value.class)
|
|
121
143
|
end
|
|
122
144
|
|
|
123
145
|
# Validates that value is an Array
|
|
@@ -128,9 +150,10 @@ module Treaty
|
|
|
128
150
|
def validate_array!(value)
|
|
129
151
|
return if value.is_a?(Array)
|
|
130
152
|
|
|
131
|
-
# TODO: It is necessary to implement a translation system (I18n).
|
|
132
153
|
raise Treaty::Exceptions::Validation,
|
|
133
|
-
"
|
|
154
|
+
I18n.t("treaty.attributes.validators.type.mismatch.array",
|
|
155
|
+
attribute: @attribute_name,
|
|
156
|
+
actual: value.class)
|
|
134
157
|
end
|
|
135
158
|
|
|
136
159
|
# Validates that value is a DateTime, Time, or Date
|
|
@@ -142,9 +165,10 @@ module Treaty
|
|
|
142
165
|
# TODO: It is better to divide it into different methods for each class.
|
|
143
166
|
return if value.is_a?(DateTime) || value.is_a?(Time) || value.is_a?(Date)
|
|
144
167
|
|
|
145
|
-
# TODO: It is necessary to implement a translation system (I18n).
|
|
146
168
|
raise Treaty::Exceptions::Validation,
|
|
147
|
-
"
|
|
169
|
+
I18n.t("treaty.attributes.validators.type.mismatch.datetime",
|
|
170
|
+
attribute: @attribute_name,
|
|
171
|
+
actual: value.class)
|
|
148
172
|
end
|
|
149
173
|
end
|
|
150
174
|
end
|
|
@@ -128,10 +128,11 @@ module Treaty
|
|
|
128
128
|
def normalize_value(value, value_key)
|
|
129
129
|
if advanced_mode?(value, value_key)
|
|
130
130
|
# Already in advanced mode, ensure it has both keys.
|
|
131
|
+
# message: nil means use I18n default message from validators
|
|
131
132
|
{ value_key => value.fetch(value_key), message: value.fetch(:message, nil) }
|
|
132
133
|
else
|
|
133
134
|
# Simple mode, convert to advanced.
|
|
134
|
-
#
|
|
135
|
+
# message: nil means use I18n default message from validators
|
|
135
136
|
{ value_key => value, message: nil }
|
|
136
137
|
end
|
|
137
138
|
end
|
|
@@ -176,10 +176,11 @@ module Treaty
|
|
|
176
176
|
|
|
177
177
|
return if unknown_options.empty?
|
|
178
178
|
|
|
179
|
-
# TODO: It is necessary to implement a translation system (I18n).
|
|
180
179
|
raise Treaty::Exceptions::Validation,
|
|
181
|
-
"
|
|
182
|
-
|
|
180
|
+
I18n.t("treaty.attributes.options.unknown",
|
|
181
|
+
attribute: @attribute.name,
|
|
182
|
+
unknown: unknown_options.join(", "),
|
|
183
|
+
known: Option::Registry.all_options.join(", "))
|
|
183
184
|
end
|
|
184
185
|
end
|
|
185
186
|
end
|
|
@@ -71,12 +71,11 @@ module Treaty
|
|
|
71
71
|
# Performs validation and transformation
|
|
72
72
|
# Must be implemented in subclasses
|
|
73
73
|
#
|
|
74
|
-
# @raise [
|
|
74
|
+
# @raise [Treaty::Exceptions::NotImplemented] If subclass doesn't implement
|
|
75
75
|
# @return [Hash] Validated and transformed data
|
|
76
76
|
def validate!
|
|
77
|
-
# TODO: It is necessary to implement a translation system (I18n).
|
|
78
77
|
raise Treaty::Exceptions::Validation,
|
|
79
|
-
"
|
|
78
|
+
I18n.t("treaty.attributes.validators.nested.orchestrator.collection_not_implemented")
|
|
80
79
|
end
|
|
81
80
|
|
|
82
81
|
private
|
|
@@ -114,10 +114,11 @@ module Treaty
|
|
|
114
114
|
|
|
115
115
|
return if validated
|
|
116
116
|
|
|
117
|
-
# TODO: It is necessary to implement a translation system (I18n).
|
|
118
117
|
raise Treaty::Exceptions::Validation,
|
|
119
|
-
"
|
|
120
|
-
|
|
118
|
+
I18n.t("treaty.attributes.validators.nested.array.element_validation_error",
|
|
119
|
+
attribute: @attribute.name,
|
|
120
|
+
index:,
|
|
121
|
+
errors: errors.join("; "))
|
|
121
122
|
end
|
|
122
123
|
|
|
123
124
|
# Validates array item for complex arrays (with regular attributes)
|
|
@@ -130,18 +131,22 @@ module Treaty
|
|
|
130
131
|
# @return [void]
|
|
131
132
|
def validate_regular_array_item!(array_item, index) # rubocop:disable Metrics/MethodLength
|
|
132
133
|
unless array_item.is_a?(Hash)
|
|
133
|
-
# TODO: It is necessary to implement a translation system (I18n).
|
|
134
134
|
raise Treaty::Exceptions::Validation,
|
|
135
|
-
"
|
|
135
|
+
I18n.t("treaty.attributes.validators.nested.array.element_type_error",
|
|
136
|
+
attribute: @attribute.name,
|
|
137
|
+
index:,
|
|
138
|
+
actual: array_item.class)
|
|
136
139
|
end
|
|
137
140
|
|
|
138
141
|
regular_validators.each do |nested_attribute, validator|
|
|
139
142
|
nested_value = array_item.fetch(nested_attribute.name, nil)
|
|
140
143
|
validator.validate_value!(nested_value)
|
|
141
144
|
rescue Treaty::Exceptions::Validation => e
|
|
142
|
-
# TODO: It is necessary to implement a translation system (I18n).
|
|
143
145
|
raise Treaty::Exceptions::Validation,
|
|
144
|
-
"
|
|
146
|
+
I18n.t("treaty.attributes.validators.nested.array.attribute_error",
|
|
147
|
+
attribute: @attribute.name,
|
|
148
|
+
index:,
|
|
149
|
+
message: e.message)
|
|
145
150
|
end
|
|
146
151
|
end
|
|
147
152
|
|
|
@@ -171,9 +171,10 @@ module Treaty
|
|
|
171
171
|
validator.validate_value!(item)
|
|
172
172
|
rescue Treaty::Exceptions::Validation => e
|
|
173
173
|
raise Treaty::Exceptions::Validation,
|
|
174
|
-
"
|
|
175
|
-
|
|
176
|
-
|
|
174
|
+
I18n.t("treaty.attributes.validators.nested.array.element_validation_error",
|
|
175
|
+
attribute: attribute.name,
|
|
176
|
+
index:,
|
|
177
|
+
errors: e.message)
|
|
177
178
|
end
|
|
178
179
|
end
|
|
179
180
|
|
|
@@ -183,11 +184,13 @@ module Treaty
|
|
|
183
184
|
# @param index [Integer] Element index for error messages
|
|
184
185
|
# @raise [Treaty::Exceptions::Validation] If item is not a Hash
|
|
185
186
|
# @return [Hash] Transformed hash
|
|
186
|
-
def transform_array_item(item, index)
|
|
187
|
+
def transform_array_item(item, index) # rubocop:disable Metrics/MethodLength
|
|
187
188
|
unless item.is_a?(Hash)
|
|
188
189
|
raise Treaty::Exceptions::Validation,
|
|
189
|
-
"
|
|
190
|
-
|
|
190
|
+
I18n.t("treaty.attributes.validators.nested.array.element_type_error",
|
|
191
|
+
attribute: attribute.name,
|
|
192
|
+
index:,
|
|
193
|
+
actual: item.class)
|
|
191
194
|
end
|
|
192
195
|
|
|
193
196
|
transformed = {}
|
|
@@ -227,7 +230,10 @@ module Treaty
|
|
|
227
230
|
end
|
|
228
231
|
rescue Treaty::Exceptions::Validation => e
|
|
229
232
|
raise Treaty::Exceptions::Validation,
|
|
230
|
-
"
|
|
233
|
+
I18n.t("treaty.attributes.validators.nested.array.attribute_error",
|
|
234
|
+
attribute: attribute.name,
|
|
235
|
+
index:,
|
|
236
|
+
message: e.message)
|
|
231
237
|
end
|
|
232
238
|
|
|
233
239
|
target_name = validator.target_name
|