verquest 0.3.0 → 0.4.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/CHANGELOG.md +11 -0
- data/README.md +62 -5
- data/lib/verquest/base/public_class_methods.rb +13 -0
- data/lib/verquest/configuration.rb +51 -1
- data/lib/verquest/gem_version.rb +1 -1
- data/lib/verquest/properties/array.rb +28 -6
- data/lib/verquest/properties/field.rb +29 -10
- data/lib/verquest/version.rb +23 -1
- data/lib/verquest.rb +3 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 725838444df6f3861ab842c88b430aa7bbc81023637595b4a0a8a909df5a2fbe
|
4
|
+
data.tar.gz: 88041a5f8bc888f90894975b5fb371ac8184877bb75b74e2e099ce57997b331a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3991e8ff96b436e82a6edce43d591f35f669cbab9ef99b01bc26192bef94e3f48afe59595d8ae5d3d605bd96c0150bfdb557e555f1d20e79d74cbe4343d99c49
|
7
|
+
data.tar.gz: dcc10eeadec0f32073569db402893bb3f1ab5629da79eee32d605c476bbcf05f637f5b348f83478863bf7d28fc1d0b3d2cd9b54c38f356f3bfa53d51d3ac50bb
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,16 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
### Breaking Changes
|
4
|
+
- **BREAKING:** Renaming validation method from `validate_schema` to `valid_schema?` to better reflect its purpose.
|
5
|
+
- **BREAKING:** The `validate_schema` now returns an array of errors instead of a boolean value, allowing for more detailed error reporting.
|
6
|
+
|
7
|
+
### New Features
|
8
|
+
- Add support for custom field types.
|
9
|
+
|
10
|
+
### Fixed
|
11
|
+
- Loading the gem in another project with `zeitwerk` now works correctly.
|
12
|
+
- Fix schema validation after `json_schemer` refactoring.
|
13
|
+
|
3
14
|
## [0.3.0] - 2025-06-25
|
4
15
|
|
5
16
|
### Breaking Changes
|
data/README.md
CHANGED
@@ -19,7 +19,7 @@ Verquest is a Ruby gem that offers an elegant solution for versioning API reques
|
|
19
19
|
Add this line to your application's Gemfile:
|
20
20
|
|
21
21
|
```ruby
|
22
|
-
gem "verquest", "~> 0.
|
22
|
+
gem "verquest", "~> 0.3"
|
23
23
|
```
|
24
24
|
|
25
25
|
And then execute:
|
@@ -174,7 +174,8 @@ Output:
|
|
174
174
|
}
|
175
175
|
}
|
176
176
|
},
|
177
|
-
"additionalProperties" => false
|
177
|
+
"additionalProperties" => false
|
178
|
+
}
|
178
179
|
```
|
179
180
|
|
180
181
|
### JSON schema for validation
|
@@ -249,7 +250,8 @@ Output:
|
|
249
250
|
You can also validate it to ensure it meets the JSON Schema standards:
|
250
251
|
|
251
252
|
```ruby
|
252
|
-
UserCreateRequest.
|
253
|
+
UserCreateRequest.valid_schema?(version: "2025-06") # => true/false
|
254
|
+
UserCreateRequest.validate_schema(version: "2025-06") # => Array of errors or empty array if valid
|
253
255
|
```
|
254
256
|
|
255
257
|
## Core Features
|
@@ -274,6 +276,61 @@ The JSON schema can be used for both validation of incoming parameters and for g
|
|
274
276
|
- `schema_options`: Allows you to set additional options for the JSON Schema, such as `additional_properties` for request or per version. All fields (except `reference`) can be defined with options like `required`, `format`, `min_lenght`, `max_length`, etc. all in snake case.
|
275
277
|
- `with_options`: Allows you to define multiple fields with the same options, reducing repetition.
|
276
278
|
|
279
|
+
#### Custom Field Types
|
280
|
+
|
281
|
+
You can define custom field types that can be used in `field` and `array` in the configuration.
|
282
|
+
|
283
|
+
```ruby
|
284
|
+
Verquest.configure do |config|
|
285
|
+
config.custom_field_types = {
|
286
|
+
email: {
|
287
|
+
type: "string",
|
288
|
+
schema_options: {format: "email"}
|
289
|
+
},
|
290
|
+
uuid: {
|
291
|
+
type: "string",
|
292
|
+
schema_options: {format: "uuid"}
|
293
|
+
}
|
294
|
+
}
|
295
|
+
end
|
296
|
+
```
|
297
|
+
|
298
|
+
Then you can use it in your request:
|
299
|
+
```ruby
|
300
|
+
class EmailRequest < Verquest::Base
|
301
|
+
description "User Create Request"
|
302
|
+
schema_options additional_properties: false
|
303
|
+
|
304
|
+
version "2025-06" do
|
305
|
+
field :email, type: :email
|
306
|
+
array :uuids, type: :uuid
|
307
|
+
end
|
308
|
+
end
|
309
|
+
```
|
310
|
+
|
311
|
+
`EmailRequest.to_schema(version: "2025-06")` will then generate the following JSON Schema:
|
312
|
+
```ruby
|
313
|
+
{
|
314
|
+
"type" => "object",
|
315
|
+
"description" => "User Create Request",
|
316
|
+
"required" => ["email"],
|
317
|
+
"properties" => {
|
318
|
+
"email" => {
|
319
|
+
"type" => "string",
|
320
|
+
"format" => "email"
|
321
|
+
},
|
322
|
+
"uuids" => {
|
323
|
+
"type" => "array",
|
324
|
+
"items" => {
|
325
|
+
"type" => "string",
|
326
|
+
"format" => "uuid"
|
327
|
+
}
|
328
|
+
}
|
329
|
+
},
|
330
|
+
"additionalProperties" => false
|
331
|
+
}
|
332
|
+
```
|
333
|
+
|
277
334
|
### Versioning
|
278
335
|
|
279
336
|
Verquest allows you to define multiple versions of your API requests, making it easy to evolve your API over time:
|
@@ -416,7 +473,7 @@ Verquest.configure do |config|
|
|
416
473
|
config.current_version = -> { Current.api_version }
|
417
474
|
|
418
475
|
# Set the JSON Schema version
|
419
|
-
config.json_schema_version = :
|
476
|
+
config.json_schema_version = :draft2020_12 # default
|
420
477
|
|
421
478
|
# Set the error handling strategy for processing params
|
422
479
|
config.validation_error_handling = :raise # default, can be set also to :result
|
@@ -431,7 +488,7 @@ end
|
|
431
488
|
|
432
489
|
## Documentation
|
433
490
|
|
434
|
-
For detailed documentation, please visit the [YARD documentation](https://www.rubydoc.info/gems/verquest).
|
491
|
+
For detailed documentation, please visit the [YARD documentation](https://www.rubydoc.info/gems/verquest/0.4.0/).
|
435
492
|
|
436
493
|
## Development
|
437
494
|
|
@@ -70,6 +70,19 @@ module Verquest
|
|
70
70
|
#
|
71
71
|
# @param version [String, nil] Specific version to use, defaults to configuration setting
|
72
72
|
# @return [Boolean] True if schema is valid
|
73
|
+
def valid_schema?(version: nil)
|
74
|
+
resolve(version).valid_schema?
|
75
|
+
end
|
76
|
+
|
77
|
+
# Validates the schema against the metaschema and returns detailed validation errors
|
78
|
+
#
|
79
|
+
# This method validates the schema against the configured JSON Schema metaschema
|
80
|
+
# and returns detailed validation errors if any are found. It's useful for debugging
|
81
|
+
# schema issues during development and testing.
|
82
|
+
#
|
83
|
+
# @param version [String, nil] Specific version to use, defaults to configuration setting
|
84
|
+
# @return [Array<Hash>] An array of validation error details, empty if schema is valid
|
85
|
+
# @see #valid_schema?
|
73
86
|
def validate_schema(version: nil)
|
74
87
|
resolve(version).validate_schema
|
75
88
|
end
|
@@ -11,6 +11,18 @@ module Verquest
|
|
11
11
|
# config.current_version = -> { Current.api_version }
|
12
12
|
# end
|
13
13
|
class Configuration
|
14
|
+
include Base::HelperClassMethods
|
15
|
+
|
16
|
+
# Mapping of supported JSON Schema versions to their implementation classes
|
17
|
+
#
|
18
|
+
# This constant maps the symbolic names of JSON Schema versions to their
|
19
|
+
# corresponding JSONSchemer implementation classes. These are used for schema
|
20
|
+
# validation and generation based on the configured schema version.
|
21
|
+
#
|
22
|
+
# @example Accessing a schema implementation
|
23
|
+
# schema_class = Verquest::Configuration::SCHEMAS[:draft2020_12]
|
24
|
+
#
|
25
|
+
# @return [Hash<Symbol, Class>] A frozen hash mapping schema version names to implementation classes
|
14
26
|
SCHEMAS = {
|
15
27
|
draft4: JSONSchemer::Draft4,
|
16
28
|
draft6: JSONSchemer::Draft6,
|
@@ -49,7 +61,11 @@ module Verquest
|
|
49
61
|
# @!attribute [r] version_resolver
|
50
62
|
# The resolver used to map version strings/identifiers to version objects
|
51
63
|
# @return [#call] An object that responds to `call` for resolving versions
|
52
|
-
|
64
|
+
#
|
65
|
+
# @!attribute [r] custom_field_types
|
66
|
+
# Custom field types to extend the standard set of field types
|
67
|
+
# @return [Hash<Symbol, Hash>] Hash mapping field type names to their configuration
|
68
|
+
attr_reader :current_version, :version_resolver, :custom_field_types
|
53
69
|
|
54
70
|
# Initialize a new Configuration with default values
|
55
71
|
#
|
@@ -61,6 +77,7 @@ module Verquest
|
|
61
77
|
@remove_extra_root_keys = true
|
62
78
|
@version_resolver = VersionResolver
|
63
79
|
@insert_property_defaults = true
|
80
|
+
@custom_field_types = {}
|
64
81
|
end
|
65
82
|
|
66
83
|
# Sets the current version strategy using a callable object
|
@@ -85,6 +102,39 @@ module Verquest
|
|
85
102
|
@version_resolver = version_resolver
|
86
103
|
end
|
87
104
|
|
105
|
+
# Sets the custom field types
|
106
|
+
#
|
107
|
+
# This method allows defining custom field types beyond the default ones.
|
108
|
+
# Custom field types can be used to extend validation with specific formats
|
109
|
+
# or patterns. Each custom field type should include a base type and optional
|
110
|
+
# schema validation options.
|
111
|
+
#
|
112
|
+
# @example Adding a phone number field type
|
113
|
+
# config.custom_field_types = {
|
114
|
+
# email: {
|
115
|
+
# type: "string",
|
116
|
+
# schema_options: {format: "email", pattern: /\A[^@\s]+@[^@.\s]+(\.[^@.\s]+)+\z/}
|
117
|
+
# },
|
118
|
+
# uuid: {
|
119
|
+
# type: "string",
|
120
|
+
# schema_options: {format: "uuid", pattern: /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/}
|
121
|
+
# }
|
122
|
+
# }
|
123
|
+
#
|
124
|
+
# @param custom_field_types [Hash] A hash mapping field type names to their configuration
|
125
|
+
# @raise [ArgumentError] If the provided value isn't a Hash
|
126
|
+
# @return [Hash<Symbol, Hash>] The processed custom field types hash with symbolized keys
|
127
|
+
def custom_field_types=(custom_field_types)
|
128
|
+
raise ArgumentError, "Custom field types must be a Hash" unless custom_field_types.is_a?(Hash)
|
129
|
+
|
130
|
+
custom_field_types.delete_if { |k, _| Properties::Field::DEFAULT_TYPES.include?(k.to_s) }
|
131
|
+
custom_field_types.each do |_, value|
|
132
|
+
value[:schema_options] = camelize(value[:schema_options]) if value[:schema_options]
|
133
|
+
end
|
134
|
+
|
135
|
+
@custom_field_types = custom_field_types.transform_keys(&:to_sym)
|
136
|
+
end
|
137
|
+
|
88
138
|
# Gets the JSON Schema class based on the configured version
|
89
139
|
#
|
90
140
|
# @return [Class] The JSON Schema class matching the configured version
|
data/lib/verquest/gem_version.rb
CHANGED
@@ -6,6 +6,7 @@ module Verquest
|
|
6
6
|
#
|
7
7
|
# Represents an array data structure in the schema with specified item type.
|
8
8
|
# Used to define arrays of scalar types (string, number, integer, boolean).
|
9
|
+
# Supports both default item types and custom field types defined in the configuration.
|
9
10
|
#
|
10
11
|
# @example Define an array of strings
|
11
12
|
# array = Verquest::Properties::Array.new(
|
@@ -17,16 +18,30 @@ module Verquest
|
|
17
18
|
# Initialize a new Array property
|
18
19
|
#
|
19
20
|
# @param name [String, Symbol] The name of the property
|
20
|
-
# @param type [String, Symbol] The type of items in the array
|
21
|
+
# @param type [String, Symbol] The type of items in the array, can be a default type or a custom field type
|
21
22
|
# @param map [String, nil] The mapping path for this property (nil for no explicit mapping)
|
22
23
|
# @param required [Boolean] Whether this property is required
|
23
|
-
# @param
|
24
|
+
# @param item_schema_options [Hash] Additional JSON schema options for the array items (merged with custom type options)
|
25
|
+
# @param schema_options [Hash] Additional JSON schema options for the array property itself
|
26
|
+
# @raise [ArgumentError] If type is not one of the allowed types (default or custom)
|
24
27
|
# @raise [ArgumentError] If attempting to map an array to the root
|
25
|
-
def initialize(name:, type:, map: nil, required: false, **schema_options)
|
28
|
+
def initialize(name:, type:, map: nil, required: false, item_schema_options: {}, **schema_options)
|
29
|
+
raise ArgumentError, "Type must be one of #{allowed_types.join(", ")}" unless allowed_types.include?(type.to_s)
|
26
30
|
raise ArgumentError, "You can not map array to the root" if map == "/"
|
27
31
|
|
32
|
+
if (custom_type = Verquest.configuration.custom_field_types[type.to_sym])
|
33
|
+
@type = custom_type[:type].to_s
|
34
|
+
@item_schema_options = if custom_type.key?(:schema_options)
|
35
|
+
custom_type[:schema_options].merge(item_schema_options).transform_keys(&:to_s)
|
36
|
+
else
|
37
|
+
item_schema_options.transform_keys(&:to_s)
|
38
|
+
end
|
39
|
+
else
|
40
|
+
@type = type.to_s
|
41
|
+
@item_schema_options = item_schema_options.transform_keys(&:to_s)
|
42
|
+
end
|
43
|
+
|
28
44
|
@name = name.to_s
|
29
|
-
@type = type.to_s
|
30
45
|
@map = map
|
31
46
|
@required = required
|
32
47
|
@schema_options = schema_options&.transform_keys(&:to_s)
|
@@ -39,7 +54,7 @@ module Verquest
|
|
39
54
|
{
|
40
55
|
name => {
|
41
56
|
"type" => "array",
|
42
|
-
"items" => {"type" => type}
|
57
|
+
"items" => {"type" => type}.merge(item_schema_options)
|
43
58
|
}.merge(schema_options)
|
44
59
|
}
|
45
60
|
end
|
@@ -57,7 +72,14 @@ module Verquest
|
|
57
72
|
|
58
73
|
private
|
59
74
|
|
60
|
-
attr_reader :type, :schema_options
|
75
|
+
attr_reader :type, :schema_options, :item_schema_options
|
76
|
+
|
77
|
+
# Gets the list of allowed item types, including both default and custom types
|
78
|
+
#
|
79
|
+
# @return [Array<String>] Array of allowed item type names
|
80
|
+
def allowed_types
|
81
|
+
Verquest::Properties::Field::DEFAULT_TYPES + Verquest.configuration.custom_field_types.keys.map(&:to_s)
|
82
|
+
end
|
61
83
|
end
|
62
84
|
end
|
63
85
|
end
|
@@ -6,6 +6,7 @@ module Verquest
|
|
6
6
|
#
|
7
7
|
# Represents simple scalar types (string, number, integer, boolean) in the schema.
|
8
8
|
# Used for defining basic data fields without nesting.
|
9
|
+
# Supports both default types and custom field types defined in the configuration.
|
9
10
|
#
|
10
11
|
# @example Define a required string field
|
11
12
|
# field = Verquest::Properties::Field.new(
|
@@ -15,28 +16,39 @@ module Verquest
|
|
15
16
|
# format: "email"
|
16
17
|
# )
|
17
18
|
class Field < Base
|
18
|
-
# List of
|
19
|
+
# List of default field types
|
19
20
|
# @return [Array<Symbol>]
|
20
|
-
|
21
|
+
DEFAULT_TYPES = %w[string number integer boolean].freeze
|
21
22
|
|
22
23
|
# Initialize a new Field property
|
23
24
|
#
|
24
25
|
# @param name [String, Symbol] The name of the property
|
25
|
-
# @param type [String, Symbol] The data type for this field,
|
26
|
-
# @param required [Boolean] Whether this property is required
|
26
|
+
# @param type [String, Symbol] The data type for this field, can be a default type or a custom field type
|
27
|
+
# @param required [Boolean] Whether this property is required (overridden by custom type if it defines required)
|
27
28
|
# @param map [String, nil] The mapping path for this property
|
28
|
-
# @param schema_options [Hash] Additional JSON schema options for this property
|
29
|
-
# @raise [ArgumentError] If type is not one of the allowed types
|
29
|
+
# @param schema_options [Hash] Additional JSON schema options for this property (merged with custom type options)
|
30
|
+
# @raise [ArgumentError] If type is not one of the allowed types (default or custom)
|
30
31
|
# @raise [ArgumentError] If attempting to map a field to root without a name
|
31
32
|
def initialize(name:, type:, required: false, map: nil, **schema_options)
|
32
|
-
raise ArgumentError, "Type must be one of #{
|
33
|
+
raise ArgumentError, "Type must be one of #{allowed_types.join(", ")}" unless allowed_types.include?(type.to_s)
|
33
34
|
raise ArgumentError, "You can not map fields to the root without a name" if map == "/"
|
34
35
|
|
36
|
+
if (custom_type = Verquest.configuration.custom_field_types[type.to_sym])
|
37
|
+
@type = custom_type[:type].to_s
|
38
|
+
@required = custom_type.key?(:required) ? custom_type[:required] : required
|
39
|
+
@schema_options = if custom_type.key?(:schema_options)
|
40
|
+
custom_type[:schema_options].merge(schema_options).transform_keys(&:to_s)
|
41
|
+
else
|
42
|
+
schema_options.transform_keys(&:to_s)
|
43
|
+
end
|
44
|
+
else
|
45
|
+
@type = type.to_s
|
46
|
+
@required = required
|
47
|
+
@schema_options = schema_options&.transform_keys(&:to_s)
|
48
|
+
end
|
49
|
+
|
35
50
|
@name = name.to_s
|
36
|
-
@type = type.to_s
|
37
|
-
@required = required
|
38
51
|
@map = map
|
39
|
-
@schema_options = schema_options&.transform_keys(&:to_s)
|
40
52
|
end
|
41
53
|
|
42
54
|
# Generate JSON schema definition for this field
|
@@ -60,6 +72,13 @@ module Verquest
|
|
60
72
|
private
|
61
73
|
|
62
74
|
attr_reader :type, :schema_options
|
75
|
+
|
76
|
+
# Gets the list of allowed field types, including both default and custom types
|
77
|
+
#
|
78
|
+
# @return [Array<String>] Array of allowed field type names
|
79
|
+
def allowed_types
|
80
|
+
DEFAULT_TYPES + Verquest.configuration.custom_field_types.keys.map(&:to_s)
|
81
|
+
end
|
63
82
|
end
|
64
83
|
end
|
65
84
|
end
|
data/lib/verquest/version.rb
CHANGED
@@ -113,11 +113,33 @@ module Verquest
|
|
113
113
|
# Validate the schema against the metaschema
|
114
114
|
#
|
115
115
|
# @return [Boolean] true if the schema is valid, false otherwise
|
116
|
+
def valid_schema?
|
117
|
+
JSONSchemer.valid_schema?(
|
118
|
+
validation_schema,
|
119
|
+
meta_schema: Verquest.configuration.json_schema_uri
|
120
|
+
)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Validate the schema against the metaschema and return detailed errors
|
124
|
+
#
|
125
|
+
# This method validates the schema against the configured JSON Schema metaschema
|
126
|
+
# and returns detailed validation errors if any are found. It uses the JSONSchemer
|
127
|
+
# library with the schema version specified in the configuration.
|
128
|
+
#
|
129
|
+
# @return [Array<Hash>] An array of validation error details, empty if schema is valid
|
130
|
+
# @see #valid_schema?
|
116
131
|
def validate_schema
|
117
132
|
JSONSchemer.validate_schema(
|
118
133
|
validation_schema,
|
119
134
|
meta_schema: Verquest.configuration.json_schema_uri
|
120
|
-
)
|
135
|
+
).map do |error|
|
136
|
+
{
|
137
|
+
pointer: error["data_pointer"],
|
138
|
+
type: error["type"],
|
139
|
+
message: error["error"],
|
140
|
+
details: error["details"]
|
141
|
+
}
|
142
|
+
end
|
121
143
|
end
|
122
144
|
|
123
145
|
# Validate request parameters against the version's validation schema
|
data/lib/verquest.rb
CHANGED
@@ -3,8 +3,11 @@
|
|
3
3
|
require "zeitwerk"
|
4
4
|
require "json_schemer"
|
5
5
|
|
6
|
+
require_relative "verquest/gem_version"
|
7
|
+
|
6
8
|
loader = Zeitwerk::Loader.new
|
7
9
|
loader.tag = File.basename(__FILE__, ".rb")
|
10
|
+
loader.ignore("#{File.dirname(__FILE__)}/verquest/gem_version.rb")
|
8
11
|
loader.push_dir(File.dirname(__FILE__))
|
9
12
|
loader.setup
|
10
13
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: verquest
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Petr Hlavicka
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-06-
|
11
|
+
date: 2025-06-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: zeitwerk
|