verquest 0.3.0 → 0.5.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 +23 -0
- data/README.md +162 -7
- data/lib/verquest/base/private_class_methods.rb +37 -27
- data/lib/verquest/base/public_class_methods.rb +13 -0
- data/lib/verquest/configuration.rb +59 -3
- data/lib/verquest/gem_version.rb +1 -1
- data/lib/verquest/helper_methods/required_properties.rb +48 -0
- data/lib/verquest/properties/array.rb +38 -8
- data/lib/verquest/properties/base.rb +6 -0
- data/lib/verquest/properties/collection.rb +27 -13
- data/lib/verquest/properties/field.rb +39 -12
- data/lib/verquest/properties/object.rb +25 -9
- data/lib/verquest/properties/reference.rb +25 -6
- data/lib/verquest/version.rb +39 -5
- data/lib/verquest.rb +3 -0
- metadata +4 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b9349b2773c4a03024805d78257b1e763ffb87b87f27726620e343395c683aa0
|
4
|
+
data.tar.gz: ec2b70a4a10c3314ca27283776138f85b53b245d88869de39e450ca2eb2064de
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0d0042f959285f4253c7802166ab64d53b7013d765bfe954fd68a8ab46bb59f162512a285aca11c77e86a8e6b5ce36445f47e4e9530fcaa0010e8c6cadcefdf2
|
7
|
+
data.tar.gz: 06bbfceb1c989e1e3eabb20e495790ac35934e1cb4687e71960315c3e64d3a30b42b345c03d19d32be921b75550a6fef002d8ba44018ded8f7c9a3af73d920f0
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,28 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.5.0] - 2025-07-01
|
4
|
+
|
5
|
+
### Fixed
|
6
|
+
- Handling `with_options` defaults like required and nullable.
|
7
|
+
|
8
|
+
### New Features
|
9
|
+
- Add `default_additional_properties` option to configuration.
|
10
|
+
- Add support for nullable properties (`nullable: true`) based on the latest JSON Schema specification, which is also used in OpenAPI 3.1.
|
11
|
+
- Add support for `dependentRequired` (see https://json-schema.org/understanding-json-schema/reference/conditionals#dependentRequired).
|
12
|
+
|
13
|
+
## [0.4.0] - 2025-06-28
|
14
|
+
|
15
|
+
### Breaking Changes
|
16
|
+
- **BREAKING:** Renaming validation method from `validate_schema` to `valid_schema?` to better reflect its purpose.
|
17
|
+
- **BREAKING:** The `validate_schema` now returns an array of errors instead of a boolean value, allowing for more detailed error reporting.
|
18
|
+
|
19
|
+
### New Features
|
20
|
+
- Add support for custom field types.
|
21
|
+
|
22
|
+
### Fixed
|
23
|
+
- Loading the gem in another project with `zeitwerk` now works correctly.
|
24
|
+
- Fix schema validation after `json_schemer` refactoring.
|
25
|
+
|
3
26
|
## [0.3.0] - 2025-06-25
|
4
27
|
|
5
28
|
### 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.5"
|
23
23
|
```
|
24
24
|
|
25
25
|
And then execute:
|
@@ -62,7 +62,7 @@ class UserCreateRequest < Verquest::Base
|
|
62
62
|
field :email, format: "email", description: "The email address of the user"
|
63
63
|
end
|
64
64
|
|
65
|
-
field :birth_date, type: :string, format: "date", description: "The birth date of the user"
|
65
|
+
field :birth_date, type: :string, nullable: true, format: "date", description: "The birth date of the user"
|
66
66
|
|
67
67
|
reference :address, from: AddressCreateRequest, required: true
|
68
68
|
|
@@ -135,7 +135,7 @@ Output:
|
|
135
135
|
"first_name" => {"type" => "string", "description" => "The first name of the user", "maxLength" => 50},
|
136
136
|
"last_name" => {"type" => "string", "description" => "The last name of the user", "maxLength" => 50},
|
137
137
|
"email" => {"type" => "string", "format" => "email", "description" => "The email address of the user"},
|
138
|
-
"birth_date" => {"type" => "string", "format" => "date", "description" => "The birth date of the user"},
|
138
|
+
"birth_date" => {"type" => ["string", "null"], "format" => "date", "description" => "The birth date of the user"},
|
139
139
|
"address" => {"$ref" => "#/components/schemas/AddressCreateRequest"},
|
140
140
|
"permissions" => {
|
141
141
|
"type" => "array",
|
@@ -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,156 @@ 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
|
+
### Required properties
|
280
|
+
|
281
|
+
You can define required properties in your request schema by setting the `required` option to `true`, or provide a list of dependent required properties. This feature is based on the latest [JSON Schema specification](https://json-schema.org/understanding-json-schema/reference/conditionals#dependentRequired), which is also used in OpenAPI 3.1.
|
282
|
+
|
283
|
+
```ruby
|
284
|
+
class DependentRequiredRequest < Verquest::Base
|
285
|
+
description "This is a simple request with nullable properties for testing purposes."
|
286
|
+
|
287
|
+
version "2025-06" do
|
288
|
+
field :name, type: :string, required: true
|
289
|
+
field :credit_card, type: :number, required: %i[billing_address]
|
290
|
+
field :billing_address, type: :string
|
291
|
+
end
|
292
|
+
end
|
293
|
+
```
|
294
|
+
|
295
|
+
Will produce this validation schema:
|
296
|
+
|
297
|
+
```ruby
|
298
|
+
{
|
299
|
+
"type" => "object",
|
300
|
+
"description" => "This is a simple request with nullable properties for testing purposes.",
|
301
|
+
"required" => ["name"],
|
302
|
+
"dependentRequired" => {"credit_card" => ["billing_address"]},
|
303
|
+
"properties" => {
|
304
|
+
"name" => {"type" => "string"},
|
305
|
+
"credit_card" => {"type" => "number"},
|
306
|
+
"billing_address" => {"type" => "string"}
|
307
|
+
},
|
308
|
+
"additionalProperties" => false
|
309
|
+
}
|
310
|
+
```
|
311
|
+
|
312
|
+
#### Nullable properties
|
313
|
+
|
314
|
+
You can define nullable properties in your request schema by setting the `nullable` option to `true`. This feature is based on the latest JSON Schema specification, which is also used in OpenAPI 3.1.
|
315
|
+
|
316
|
+
```ruby
|
317
|
+
class NullableRequest < Verquest::Base
|
318
|
+
description "This is a simple request with nullable properties for testing purposes."
|
319
|
+
|
320
|
+
version "2025-06" do
|
321
|
+
with_options nullable: true do
|
322
|
+
array :array, type: :string
|
323
|
+
collection :collection_with_item, item: ReferencedRequest
|
324
|
+
collection :collection_with_object do
|
325
|
+
field :field, type: :string, nullable: false
|
326
|
+
end
|
327
|
+
|
328
|
+
field :field, type: :string
|
329
|
+
|
330
|
+
object :object do
|
331
|
+
field :field, type: :string, nullable: false
|
332
|
+
end
|
333
|
+
|
334
|
+
reference :referenced_object, from: ReferencedRequest
|
335
|
+
reference :referenced_field, from: ReferencedRequest, property: :simple_field
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
339
|
+
```
|
340
|
+
|
341
|
+
Will produce this validation schema:
|
342
|
+
|
343
|
+
```ruby
|
344
|
+
{
|
345
|
+
"type" => "object",
|
346
|
+
"description" => "This is a simple request with nullable properties for testing purposes.",
|
347
|
+
"required" => [],
|
348
|
+
"properties" => {
|
349
|
+
"array" => {"type" => %w[array null], "items" => {"type" => "string"}},
|
350
|
+
"collection_with_item" => {"type" => %w[array null], "items" => {"type" => "object", "description" => "This is an another example for testing purposes.", "required" => %w[simple_field nested], "properties" => {"simple_field" => {"type" => "string", "description" => "The simple field"}, "nested" => {"type" => "object", "required" => %w[nested_field_1 nested_field_2], "properties" => {"nested_field_1" => {"type" => "string", "description" => "This is a nested field"}, "nested_field_2" => {"type" => "string", "description" => "This is another nested field"}}, "additionalProperties" => false}}, "additionalProperties" => false}},
|
351
|
+
"collection_with_object" => {"type" => %w[array null], "items" => {"type" => "object", "required" => [], "properties" => {"field" => {"type" => "string"}}, "additionalProperties" => false}},
|
352
|
+
"field" => {"type" => %w[string null]},
|
353
|
+
"object" => {
|
354
|
+
"type" => %w[object null],
|
355
|
+
"required" => [],
|
356
|
+
"properties" => {
|
357
|
+
"field" => {"type" => "string"}
|
358
|
+
},
|
359
|
+
"additionalProperties" => false
|
360
|
+
},
|
361
|
+
"referenced_object" => {
|
362
|
+
"type" => %w[object null],
|
363
|
+
"description" => "This is an another example for testing purposes.",
|
364
|
+
"required" => %w[simple_field nested],
|
365
|
+
"properties" => {"simple_field" => {"type" => "string", "description" => "The simple field"}, "nested" => {"type" => "object", "required" => %w[nested_field_1 nested_field_2], "properties" => {"nested_field_1" => {"type" => "string", "description" => "This is a nested field"}, "nested_field_2" => {"type" => "string", "description" => "This is another nested field"}}, "additionalProperties" => false}},
|
366
|
+
"additionalProperties" => false
|
367
|
+
},
|
368
|
+
"referenced_field" => {"type" => %w[string null], "description" => "The simple field"}
|
369
|
+
},
|
370
|
+
"additionalProperties" => false
|
371
|
+
}
|
372
|
+
```
|
373
|
+
|
374
|
+
#### Custom Field Types
|
375
|
+
|
376
|
+
You can define custom field types that can be used in `field` and `array` in the configuration.
|
377
|
+
|
378
|
+
```ruby
|
379
|
+
Verquest.configure do |config|
|
380
|
+
config.custom_field_types = {
|
381
|
+
email: {
|
382
|
+
type: "string",
|
383
|
+
schema_options: {format: "email"}
|
384
|
+
},
|
385
|
+
uuid: {
|
386
|
+
type: "string",
|
387
|
+
schema_options: {format: "uuid"}
|
388
|
+
}
|
389
|
+
}
|
390
|
+
end
|
391
|
+
```
|
392
|
+
|
393
|
+
Then you can use it in your request:
|
394
|
+
```ruby
|
395
|
+
class EmailRequest < Verquest::Base
|
396
|
+
description "User Create Request"
|
397
|
+
schema_options additional_properties: false
|
398
|
+
|
399
|
+
version "2025-06" do
|
400
|
+
field :email, type: :email
|
401
|
+
array :uuids, type: :uuid
|
402
|
+
end
|
403
|
+
end
|
404
|
+
```
|
405
|
+
|
406
|
+
`EmailRequest.to_schema(version: "2025-06")` will then generate the following JSON Schema:
|
407
|
+
```ruby
|
408
|
+
{
|
409
|
+
"type" => "object",
|
410
|
+
"description" => "User Create Request",
|
411
|
+
"required" => ["email"],
|
412
|
+
"properties" => {
|
413
|
+
"email" => {
|
414
|
+
"type" => "string",
|
415
|
+
"format" => "email"
|
416
|
+
},
|
417
|
+
"uuids" => {
|
418
|
+
"type" => "array",
|
419
|
+
"items" => {
|
420
|
+
"type" => "string",
|
421
|
+
"format" => "uuid"
|
422
|
+
}
|
423
|
+
}
|
424
|
+
},
|
425
|
+
"additionalProperties" => false
|
426
|
+
}
|
427
|
+
```
|
428
|
+
|
277
429
|
### Versioning
|
278
430
|
|
279
431
|
Verquest allows you to define multiple versions of your API requests, making it easy to evolve your API over time:
|
@@ -416,7 +568,7 @@ Verquest.configure do |config|
|
|
416
568
|
config.current_version = -> { Current.api_version }
|
417
569
|
|
418
570
|
# Set the JSON Schema version
|
419
|
-
config.json_schema_version = :
|
571
|
+
config.json_schema_version = :draft2020_12 # default
|
420
572
|
|
421
573
|
# Set the error handling strategy for processing params
|
422
574
|
config.validation_error_handling = :raise # default, can be set also to :result
|
@@ -426,12 +578,15 @@ Verquest.configure do |config|
|
|
426
578
|
|
427
579
|
# Set custom version resolver
|
428
580
|
config.version_resolver = CustomeVersionResolver # default is `Verquest::VersionResolver`
|
581
|
+
|
582
|
+
# Set default value for additional properties
|
583
|
+
config.default_additional_properties = false # default
|
429
584
|
end
|
430
585
|
```
|
431
586
|
|
432
587
|
## Documentation
|
433
588
|
|
434
|
-
For detailed documentation, please visit the [YARD documentation](https://www.rubydoc.info/gems/verquest).
|
589
|
+
For detailed documentation, please visit the [YARD documentation](https://www.rubydoc.info/gems/verquest/0.5.0/).
|
435
590
|
|
436
591
|
## Development
|
437
592
|
|
@@ -92,9 +92,9 @@ module Verquest
|
|
92
92
|
camelize(schema_options)
|
93
93
|
|
94
94
|
if current_scope.nil?
|
95
|
-
versions.schema_options
|
95
|
+
versions.schema_options.merge!(schema_options)
|
96
96
|
elsif current_scope.is_a?(Version)
|
97
|
-
current_scope.schema_options
|
97
|
+
current_scope.schema_options.merge!(schema_options)
|
98
98
|
else
|
99
99
|
raise "Additional properties can only be set within a version scope or globally"
|
100
100
|
end
|
@@ -122,17 +122,19 @@ module Verquest
|
|
122
122
|
# @param name [Symbol] The name of the field
|
123
123
|
# @param type [Symbol] The data type of the field
|
124
124
|
# @param map [String, nil] An optional mapping to another field
|
125
|
-
# @param required [Boolean] Whether the field is required
|
125
|
+
# @param required [Boolean, Array<Symbol>] Whether the field is required
|
126
|
+
# @param nullable [Boolean] Whether the field can be null
|
126
127
|
# @param schema_options [Hash] Additional schema options for the field
|
127
128
|
# @return [void]
|
128
|
-
def field(name, type: nil, map: nil, required:
|
129
|
+
def field(name, type: nil, map: nil, required: nil, nullable: nil, **schema_options)
|
129
130
|
camelize(schema_options)
|
130
131
|
|
131
132
|
type = default_options.fetch(:type, type)
|
132
|
-
required = default_options.fetch(:required, required
|
133
|
-
|
133
|
+
required = default_options.fetch(:required, false) if required.nil?
|
134
|
+
nullable = default_options.fetch(:nullable, false) if nullable.nil?
|
135
|
+
schema_options = default_options.except(:type, :required, :nullable).merge(schema_options)
|
134
136
|
|
135
|
-
field = Properties::Field.new(name:, type:, map:, required:, **schema_options)
|
137
|
+
field = Properties::Field.new(name:, type:, map:, required:, nullable:, **schema_options)
|
136
138
|
current_scope.add(field)
|
137
139
|
end
|
138
140
|
|
@@ -140,20 +142,22 @@ module Verquest
|
|
140
142
|
#
|
141
143
|
# @param name [Symbol] The name of the object
|
142
144
|
# @param map [String, nil] An optional mapping to another object
|
143
|
-
# @param required [Boolean] Whether the object is required
|
145
|
+
# @param required [Boolean, Array<Symbol>] Whether the object is required
|
146
|
+
# @param nullable [Boolean] Whether the object can be null
|
144
147
|
# @param schema_options [Hash] Additional schema options for the object
|
145
148
|
# @yield Block executed in the context of the new object definition
|
146
149
|
# @return [void]
|
147
|
-
def object(name, map: nil, required:
|
150
|
+
def object(name, map: nil, required: nil, nullable: nil, **schema_options, &block)
|
148
151
|
unless block_given?
|
149
152
|
raise ArgumentError, "a block must be given to define the object"
|
150
153
|
end
|
151
154
|
|
152
155
|
camelize(schema_options)
|
153
|
-
required = default_options.fetch(:required, required
|
154
|
-
|
156
|
+
required = default_options.fetch(:required, false) if required.nil?
|
157
|
+
nullable = default_options.fetch(:nullable, false) if nullable.nil?
|
158
|
+
schema_options = default_options.except(:type, :required, :nullable).merge(schema_options)
|
155
159
|
|
156
|
-
object = Properties::Object.new(name:, map:, required:, **schema_options)
|
160
|
+
object = Properties::Object.new(name:, map:, required:, nullable:, **schema_options)
|
157
161
|
current_scope.add(object)
|
158
162
|
|
159
163
|
if block_given?
|
@@ -170,12 +174,13 @@ module Verquest
|
|
170
174
|
#
|
171
175
|
# @param name [Symbol] The name of the collection
|
172
176
|
# @param item [Class, nil] The item type in the collection
|
173
|
-
# @param required [Boolean] Whether the collection is required
|
177
|
+
# @param required [Boolean, Array<Symbol>] Whether the collection is required
|
178
|
+
# @param nullable [Boolean] Whether the collection can be null
|
174
179
|
# @param map [String, nil] An optional mapping to another collection
|
175
180
|
# @param schema_options [Hash] Additional schema options for the collection
|
176
181
|
# @yield Block executed in the context of the new collection definition
|
177
182
|
# @return [void]
|
178
|
-
def collection(name, item: nil, required:
|
183
|
+
def collection(name, item: nil, required: nil, nullable: nil, map: nil, **schema_options, &block)
|
179
184
|
if item.nil? && !block_given?
|
180
185
|
raise ArgumentError, "item must be provided or a block must be given to define the collection"
|
181
186
|
elsif !item.nil? && !block_given? && !(item <= Verquest::Base)
|
@@ -183,10 +188,11 @@ module Verquest
|
|
183
188
|
end
|
184
189
|
|
185
190
|
camelize(schema_options)
|
186
|
-
required = default_options.fetch(:required, required
|
187
|
-
|
191
|
+
required = default_options.fetch(:required, false) if required.nil?
|
192
|
+
nullable = default_options.fetch(:nullable, false) if nullable.nil?
|
193
|
+
schema_options = default_options.except(:required, :nullable).merge(schema_options)
|
188
194
|
|
189
|
-
collection = Properties::Collection.new(name:, item:, required:, map:, **schema_options)
|
195
|
+
collection = Properties::Collection.new(name:, item:, required:, nullable:, map:, **schema_options)
|
190
196
|
current_scope.add(collection)
|
191
197
|
|
192
198
|
if block_given?
|
@@ -205,31 +211,35 @@ module Verquest
|
|
205
211
|
# @param from [Verquest::Base] The source of the reference
|
206
212
|
# @param property [Symbol, nil] An optional specific property to reference
|
207
213
|
# @param map [String, nil] An optional mapping to another reference
|
208
|
-
# @param required [Boolean] Whether the reference is required
|
214
|
+
# @param required [Boolean, Array<Symbol>] Whether the reference is required
|
215
|
+
# @param nullable [Boolean] Whether this reference can be null
|
209
216
|
# @return [void]
|
210
|
-
def reference(name, from:, property: nil, map: nil, required:
|
211
|
-
required = default_options.fetch(:required, required
|
217
|
+
def reference(name, from:, property: nil, map: nil, required: nil, nullable: nil)
|
218
|
+
required = default_options.fetch(:required, false) if required.nil?
|
219
|
+
nullable = default_options.fetch(:nullable, false) if nullable.nil?
|
212
220
|
|
213
|
-
reference = Properties::Reference.new(name:, from:, property:, map:, required:)
|
221
|
+
reference = Properties::Reference.new(name:, from:, property:, map:, required:, nullable:)
|
214
222
|
current_scope.add(reference)
|
215
223
|
end
|
216
224
|
|
217
225
|
# Defines a new array property for the current version scope
|
218
226
|
#
|
219
227
|
# @param name [Symbol] The name of the array property
|
220
|
-
# @param type [
|
221
|
-
# @param required [Boolean] Whether the array property is required
|
228
|
+
# @param type [Symbol] The data type of the array elements
|
229
|
+
# @param required [Boolean, Array<Symbol>] Whether the array property is required
|
230
|
+
# @param nullable [Boolean] Whether this array can be null
|
222
231
|
# @param map [String, nil] An optional mapping to another array property
|
223
232
|
# @param schema_options [Hash] Additional schema options for the array property
|
224
233
|
# @return [void]
|
225
|
-
def array(name, type:, required:
|
234
|
+
def array(name, type:, required: nil, nullable: nil, map: nil, **schema_options)
|
226
235
|
camelize(schema_options)
|
227
236
|
|
228
237
|
type = default_options.fetch(:type, type)
|
229
|
-
required = default_options.fetch(:required, required
|
230
|
-
|
238
|
+
required = default_options.fetch(:required, false) if required.nil?
|
239
|
+
nullable = default_options.fetch(:nullable, false) if nullable.nil?
|
240
|
+
schema_options = default_options.except(:type, :required, :nullable).merge(schema_options)
|
231
241
|
|
232
|
-
array = Properties::Array.new(name:, type:, required:, map:, **schema_options)
|
242
|
+
array = Properties::Array.new(name:, type:, required:, nullable:, map:, **schema_options)
|
233
243
|
current_scope.add(array)
|
234
244
|
end
|
235
245
|
|
@@ -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,
|
@@ -40,7 +52,12 @@ module Verquest
|
|
40
52
|
# @!attribute [rw] insert_property_defaults
|
41
53
|
# Controls whether default values defined in property schemas should be inserted when not provided during validation
|
42
54
|
# @return [Boolean] true if default values should be inserted, false otherwise
|
43
|
-
|
55
|
+
#
|
56
|
+
# @!attribute [rw] default_additional_properties
|
57
|
+
# Controls the default behavior for handling properties not defined in the schema
|
58
|
+
# @return [Boolean] false to disallow additional properties (default), true to allow them
|
59
|
+
attr_accessor :validate_params, :json_schema_version, :validation_error_handling,
|
60
|
+
:remove_extra_root_keys, :insert_property_defaults, :default_additional_properties
|
44
61
|
|
45
62
|
# @!attribute [r] current_version
|
46
63
|
# A callable object that returns the current API version to use when not explicitly specified
|
@@ -49,7 +66,11 @@ module Verquest
|
|
49
66
|
# @!attribute [r] version_resolver
|
50
67
|
# The resolver used to map version strings/identifiers to version objects
|
51
68
|
# @return [#call] An object that responds to `call` for resolving versions
|
52
|
-
|
69
|
+
#
|
70
|
+
# @!attribute [r] custom_field_types
|
71
|
+
# Custom field types to extend the standard set of field types
|
72
|
+
# @return [Hash<Symbol, Hash>] Hash mapping field type names to their configuration
|
73
|
+
attr_reader :current_version, :version_resolver, :custom_field_types
|
53
74
|
|
54
75
|
# Initialize a new Configuration with default values
|
55
76
|
#
|
@@ -57,10 +78,12 @@ module Verquest
|
|
57
78
|
def initialize
|
58
79
|
@validate_params = true
|
59
80
|
@json_schema_version = :draft2020_12
|
60
|
-
@validation_error_handling = :raise
|
81
|
+
@validation_error_handling = :raise
|
61
82
|
@remove_extra_root_keys = true
|
62
83
|
@version_resolver = VersionResolver
|
63
84
|
@insert_property_defaults = true
|
85
|
+
@custom_field_types = {}
|
86
|
+
@default_additional_properties = false
|
64
87
|
end
|
65
88
|
|
66
89
|
# Sets the current version strategy using a callable object
|
@@ -85,6 +108,39 @@ module Verquest
|
|
85
108
|
@version_resolver = version_resolver
|
86
109
|
end
|
87
110
|
|
111
|
+
# Sets the custom field types
|
112
|
+
#
|
113
|
+
# This method allows defining custom field types beyond the default ones.
|
114
|
+
# Custom field types can be used to extend validation with specific formats
|
115
|
+
# or patterns. Each custom field type should include a base type and optional
|
116
|
+
# schema validation options.
|
117
|
+
#
|
118
|
+
# @example Adding a phone number field type
|
119
|
+
# config.custom_field_types = {
|
120
|
+
# email: {
|
121
|
+
# type: "string",
|
122
|
+
# schema_options: {format: "email", pattern: /\A[^@\s]+@[^@.\s]+(\.[^@.\s]+)+\z/}
|
123
|
+
# },
|
124
|
+
# uuid: {
|
125
|
+
# type: "string",
|
126
|
+
# 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}/}
|
127
|
+
# }
|
128
|
+
# }
|
129
|
+
#
|
130
|
+
# @param custom_field_types [Hash] A hash mapping field type names to their configuration
|
131
|
+
# @raise [ArgumentError] If the provided value isn't a Hash
|
132
|
+
# @return [Hash<Symbol, Hash>] The processed custom field types hash with symbolized keys
|
133
|
+
def custom_field_types=(custom_field_types)
|
134
|
+
raise ArgumentError, "Custom field types must be a Hash" unless custom_field_types.is_a?(Hash)
|
135
|
+
|
136
|
+
custom_field_types.delete_if { |k, _| Properties::Field::DEFAULT_TYPES.include?(k.to_s) }
|
137
|
+
custom_field_types.each do |_, value|
|
138
|
+
value[:schema_options] = camelize(value[:schema_options]) if value[:schema_options]
|
139
|
+
end
|
140
|
+
|
141
|
+
@custom_field_types = custom_field_types.transform_keys(&:to_sym)
|
142
|
+
end
|
143
|
+
|
88
144
|
# Gets the JSON Schema class based on the configured version
|
89
145
|
#
|
90
146
|
# @return [Class] The JSON Schema class matching the configured version
|
data/lib/verquest/gem_version.rb
CHANGED
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Verquest
|
4
|
+
# HelperMethods module provides utility methods for Verquest
|
5
|
+
module HelperMethods
|
6
|
+
# Module that provides methods for working with required properties in schemas
|
7
|
+
#
|
8
|
+
# This module offers functionality to identify and categorize properties based on
|
9
|
+
# their required status within schema definitions. It distinguishes between
|
10
|
+
# unconditionally required properties (those marked with `required: true`) and
|
11
|
+
# conditionally required properties (those with dependencies expressed as arrays).
|
12
|
+
#
|
13
|
+
# When included in classes that manage properties (like Version or Base property classes),
|
14
|
+
# it provides methods to extract both types of required properties which can be used
|
15
|
+
# for schema validation, documentation generation, or UI rendering.
|
16
|
+
module RequiredProperties
|
17
|
+
# Returns all properties that are unconditionally required
|
18
|
+
#
|
19
|
+
# This method identifies properties that must always be present in valid data,
|
20
|
+
# by selecting those with their required attribute set to true (boolean).
|
21
|
+
# Results are memoized to avoid recalculating on subsequent calls.
|
22
|
+
#
|
23
|
+
# @return [Array<Symbol>] Names of properties marked as unconditionally required (required == true)
|
24
|
+
def required_properties
|
25
|
+
@_required_properties ||= properties.values.select { _1.required == true }.map(&:name)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns properties that are conditionally required based on other properties
|
29
|
+
#
|
30
|
+
# This method identifies properties that are required only when certain other
|
31
|
+
# properties are present. These are properties where the required attribute
|
32
|
+
# is an array of dependency names rather than a boolean.
|
33
|
+
# Results are memoized to avoid recalculating on subsequent calls.
|
34
|
+
#
|
35
|
+
# @return [Hash<String, Array<String>>] Hash mapping property names to their dependency arrays
|
36
|
+
# @example Return value format:
|
37
|
+
# {
|
38
|
+
# "property_name1": ["dependency1", "dependency2"],
|
39
|
+
# "property_name2": ["dependency3"]
|
40
|
+
# }
|
41
|
+
def dependent_required_properties
|
42
|
+
@_dependent_required_properties ||= properties.values.select { _1.required.is_a?(Array) }.each_with_object({}) do |property, hash|
|
43
|
+
hash[property.name] = property.required.map(&:to_s)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -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,19 +18,41 @@ 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
|
-
# @param required [Boolean] Whether this property is required
|
23
|
-
# @param
|
23
|
+
# @param required [Boolean, Array<Symbol>] Whether this property is required, or array of dependency names
|
24
|
+
# @param nullable [Boolean] Whether this property can be null
|
25
|
+
# @param item_schema_options [Hash] Additional JSON schema options for the array items (merged with custom type options)
|
26
|
+
# @param schema_options [Hash] Additional JSON schema options for the array property itself
|
27
|
+
# @raise [ArgumentError] If type is not one of the allowed types (default or custom)
|
24
28
|
# @raise [ArgumentError] If attempting to map an array to the root
|
25
|
-
def initialize(name:, type:, map: nil, required: false, **schema_options)
|
29
|
+
def initialize(name:, type:, map: nil, required: false, nullable: false, item_schema_options: {}, **schema_options)
|
30
|
+
raise ArgumentError, "Type must be one of #{allowed_types.join(", ")}" unless allowed_types.include?(type.to_s)
|
26
31
|
raise ArgumentError, "You can not map array to the root" if map == "/"
|
27
32
|
|
33
|
+
if (custom_type = Verquest.configuration.custom_field_types[type.to_sym])
|
34
|
+
@item_type = custom_type[:type].to_s
|
35
|
+
@item_schema_options = if custom_type.key?(:schema_options)
|
36
|
+
custom_type[:schema_options].merge(item_schema_options).transform_keys(&:to_s)
|
37
|
+
else
|
38
|
+
item_schema_options.transform_keys(&:to_s)
|
39
|
+
end
|
40
|
+
else
|
41
|
+
@item_type = type.to_s
|
42
|
+
@item_schema_options = item_schema_options.transform_keys(&:to_s)
|
43
|
+
end
|
44
|
+
|
28
45
|
@name = name.to_s
|
29
|
-
@type = type.to_s
|
30
46
|
@map = map
|
31
47
|
@required = required
|
48
|
+
@nullable = nullable
|
32
49
|
@schema_options = schema_options&.transform_keys(&:to_s)
|
50
|
+
|
51
|
+
@type = if nullable
|
52
|
+
%w[array null]
|
53
|
+
else
|
54
|
+
"array"
|
55
|
+
end
|
33
56
|
end
|
34
57
|
|
35
58
|
# Generate JSON schema definition for this array property
|
@@ -38,8 +61,8 @@ module Verquest
|
|
38
61
|
def to_schema
|
39
62
|
{
|
40
63
|
name => {
|
41
|
-
"type" =>
|
42
|
-
"items" => {"type" =>
|
64
|
+
"type" => type,
|
65
|
+
"items" => {"type" => item_type}.merge(item_schema_options)
|
43
66
|
}.merge(schema_options)
|
44
67
|
}
|
45
68
|
end
|
@@ -57,7 +80,14 @@ module Verquest
|
|
57
80
|
|
58
81
|
private
|
59
82
|
|
60
|
-
attr_reader :type, :schema_options
|
83
|
+
attr_reader :type, :item_type, :schema_options, :item_schema_options
|
84
|
+
|
85
|
+
# Gets the list of allowed item types, including both default and custom types
|
86
|
+
#
|
87
|
+
# @return [Array<String>] Array of allowed item type names
|
88
|
+
def allowed_types
|
89
|
+
Verquest::Properties::Field::DEFAULT_TYPES + Verquest.configuration.custom_field_types.keys.map(&:to_s)
|
90
|
+
end
|
61
91
|
end
|
62
92
|
end
|
63
93
|
end
|
@@ -10,6 +10,8 @@ module Verquest
|
|
10
10
|
#
|
11
11
|
# @abstract Subclass and override {#to_schema}, {#mapping} to implement
|
12
12
|
class Base
|
13
|
+
include HelperMethods::RequiredProperties
|
14
|
+
|
13
15
|
# @!attribute [rw] name
|
14
16
|
# @return [String] The name of the property
|
15
17
|
# @!attribute [rw] required
|
@@ -55,6 +57,10 @@ module Verquest
|
|
55
57
|
|
56
58
|
private
|
57
59
|
|
60
|
+
# @!attribute [r] nullable
|
61
|
+
# @return [Boolean] Whether this property can be null
|
62
|
+
attr_reader :nullable
|
63
|
+
|
58
64
|
# Determines the mapping target key based on mapping configuration
|
59
65
|
# @param value_prefix [Array<String>] Prefix for the target value
|
60
66
|
# @param collection [Boolean] Whether this is a collection mapping
|
@@ -22,11 +22,12 @@ module Verquest
|
|
22
22
|
#
|
23
23
|
# @param name [String, Symbol] The name of the property
|
24
24
|
# @param item [Verquest::Base, nil] Optional reference to an external schema class
|
25
|
-
# @param required [Boolean] Whether this property is required
|
25
|
+
# @param required [Boolean, Array<Symbol>] Whether this property is required, or array of dependency names
|
26
|
+
# @param nullable [Boolean] Whether this property can be null
|
26
27
|
# @param map [String, nil] The mapping path for this property
|
27
28
|
# @param schema_options [Hash] Additional JSON schema options for this property
|
28
29
|
# @raise [ArgumentError] If attempting to map a collection to the root
|
29
|
-
def initialize(name:, item: nil, required: false, map: nil, **schema_options)
|
30
|
+
def initialize(name:, item: nil, required: false, nullable: false, map: nil, **schema_options)
|
30
31
|
raise ArgumentError, "You can not map collection to the root" if map == "/"
|
31
32
|
|
32
33
|
@properties = {}
|
@@ -34,8 +35,15 @@ module Verquest
|
|
34
35
|
@name = name.to_s
|
35
36
|
@item = item
|
36
37
|
@required = required
|
38
|
+
@nullable = nullable
|
37
39
|
@map = map
|
38
40
|
@schema_options = schema_options&.transform_keys(&:to_s)
|
41
|
+
|
42
|
+
@type = if nullable
|
43
|
+
%w[array null]
|
44
|
+
else
|
45
|
+
"array"
|
46
|
+
end
|
39
47
|
end
|
40
48
|
|
41
49
|
# Add a child property to this collection's item definition
|
@@ -60,7 +68,7 @@ module Verquest
|
|
60
68
|
if has_item?
|
61
69
|
{
|
62
70
|
name => {
|
63
|
-
"type" =>
|
71
|
+
"type" => type,
|
64
72
|
"items" => {
|
65
73
|
"$ref" => item.to_ref
|
66
74
|
}
|
@@ -69,12 +77,15 @@ module Verquest
|
|
69
77
|
else
|
70
78
|
{
|
71
79
|
name => {
|
72
|
-
"type" =>
|
80
|
+
"type" => type,
|
73
81
|
"items" => {
|
74
82
|
"type" => "object",
|
75
|
-
"required" =>
|
76
|
-
"properties" => properties.transform_values { |property| property.to_schema[property.name] }
|
77
|
-
|
83
|
+
"required" => required_properties,
|
84
|
+
"properties" => properties.transform_values { |property| property.to_schema[property.name] },
|
85
|
+
"additionalProperties" => Verquest.configuration.default_additional_properties
|
86
|
+
}.tap do |schema|
|
87
|
+
schema["dependentRequired"] = dependent_required_properties if dependent_required_properties.any?
|
88
|
+
end
|
78
89
|
}.merge(schema_options)
|
79
90
|
}
|
80
91
|
end
|
@@ -88,19 +99,22 @@ module Verquest
|
|
88
99
|
if has_item?
|
89
100
|
{
|
90
101
|
name => {
|
91
|
-
"type" =>
|
102
|
+
"type" => type,
|
92
103
|
"items" => item.to_validation_schema(version: version)
|
93
104
|
}.merge(schema_options)
|
94
105
|
}
|
95
106
|
else
|
96
107
|
{
|
97
108
|
name => {
|
98
|
-
"type" =>
|
109
|
+
"type" => type,
|
99
110
|
"items" => {
|
100
111
|
"type" => "object",
|
101
|
-
"required" =>
|
102
|
-
"properties" => properties.transform_values { |property| property.to_validation_schema(version: version)[property.name] }
|
103
|
-
|
112
|
+
"required" => required_properties,
|
113
|
+
"properties" => properties.transform_values { |property| property.to_validation_schema(version: version)[property.name] },
|
114
|
+
"additionalProperties" => Verquest.configuration.default_additional_properties
|
115
|
+
}.tap do |schema|
|
116
|
+
schema["dependentRequired"] = dependent_required_properties if dependent_required_properties.any?
|
117
|
+
end
|
104
118
|
}.merge(schema_options)
|
105
119
|
}
|
106
120
|
end
|
@@ -141,7 +155,7 @@ module Verquest
|
|
141
155
|
|
142
156
|
private
|
143
157
|
|
144
|
-
attr_reader :item, :schema_options, :properties
|
158
|
+
attr_reader :item, :schema_options, :properties, :type
|
145
159
|
end
|
146
160
|
end
|
147
161
|
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,35 +16,54 @@ 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, Array<Symbol>] Whether this property is required, or array of dependency names (can be overridden by custom type)
|
28
|
+
# @param nullable [Boolean] Whether this property can be null
|
27
29
|
# @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
|
30
|
+
# @param schema_options [Hash] Additional JSON schema options for this property (merged with custom type options)
|
31
|
+
# @raise [ArgumentError] If type is not one of the allowed types (default or custom)
|
30
32
|
# @raise [ArgumentError] If attempting to map a field to root without a name
|
31
|
-
def initialize(name:, type:, required: false, map: nil, **schema_options)
|
32
|
-
raise ArgumentError, "Type must be one of #{
|
33
|
+
def initialize(name:, type:, required: false, nullable: false, map: nil, **schema_options)
|
34
|
+
raise ArgumentError, "Type must be one of #{allowed_types.join(", ")}" unless allowed_types.include?(type.to_s)
|
33
35
|
raise ArgumentError, "You can not map fields to the root without a name" if map == "/"
|
34
36
|
|
37
|
+
if (custom_type = Verquest.configuration.custom_field_types[type.to_sym])
|
38
|
+
@type = custom_type[:type].to_s
|
39
|
+
@required = custom_type.key?(:required) ? custom_type[:required] : required
|
40
|
+
@schema_options = if custom_type.key?(:schema_options)
|
41
|
+
custom_type[:schema_options].merge(schema_options).transform_keys(&:to_s)
|
42
|
+
else
|
43
|
+
schema_options.transform_keys(&:to_s)
|
44
|
+
end
|
45
|
+
else
|
46
|
+
@type = type.to_s
|
47
|
+
@required = required
|
48
|
+
@schema_options = schema_options&.transform_keys(&:to_s)
|
49
|
+
end
|
50
|
+
|
35
51
|
@name = name.to_s
|
36
|
-
@
|
37
|
-
@required = required
|
52
|
+
@nullable = nullable
|
38
53
|
@map = map
|
39
|
-
|
54
|
+
|
55
|
+
if nullable
|
56
|
+
@type = [@type, "null"]
|
57
|
+
end
|
40
58
|
end
|
41
59
|
|
42
60
|
# Generate JSON schema definition for this field
|
43
61
|
#
|
44
62
|
# @return [Hash] The schema definition for this field
|
45
63
|
def to_schema
|
46
|
-
{
|
64
|
+
{
|
65
|
+
name => {"type" => type}.merge(schema_options)
|
66
|
+
}
|
47
67
|
end
|
48
68
|
|
49
69
|
# Create mapping for this field property
|
@@ -60,6 +80,13 @@ module Verquest
|
|
60
80
|
private
|
61
81
|
|
62
82
|
attr_reader :type, :schema_options
|
83
|
+
|
84
|
+
# Gets the list of allowed field types, including both default and custom types
|
85
|
+
#
|
86
|
+
# @return [Array<String>] Array of allowed field type names
|
87
|
+
def allowed_types
|
88
|
+
DEFAULT_TYPES + Verquest.configuration.custom_field_types.keys.map(&:to_s)
|
89
|
+
end
|
63
90
|
end
|
64
91
|
end
|
65
92
|
end
|
@@ -15,16 +15,28 @@ module Verquest
|
|
15
15
|
# Initialize a new Object property
|
16
16
|
#
|
17
17
|
# @param name [String, Symbol] The name of the property
|
18
|
-
# @param required [Boolean] Whether this property is required
|
18
|
+
# @param required [Boolean, Array<Symbol>] Whether this property is required, or array of dependency names
|
19
|
+
# @param nullable [Boolean] Whether this property can be null
|
19
20
|
# @param map [String, nil] The mapping path for this property
|
20
21
|
# @param schema_options [Hash] Additional JSON schema options for this property
|
21
|
-
def initialize(name:, required: false, map: nil, **schema_options)
|
22
|
+
def initialize(name:, required: false, nullable: false, map: nil, **schema_options)
|
22
23
|
@properties = {}
|
23
24
|
|
24
25
|
@name = name.to_s
|
25
26
|
@required = required
|
27
|
+
@nullable = nullable
|
26
28
|
@map = map
|
27
|
-
@schema_options =
|
29
|
+
@schema_options = {
|
30
|
+
additionalProperties: Verquest.configuration.default_additional_properties
|
31
|
+
}.merge(schema_options)
|
32
|
+
.delete_if { |_, v| v.nil? }
|
33
|
+
.transform_keys(&:to_s)
|
34
|
+
|
35
|
+
@type = if nullable
|
36
|
+
%w[object null]
|
37
|
+
else
|
38
|
+
"object"
|
39
|
+
end
|
28
40
|
end
|
29
41
|
|
30
42
|
# Add a child property to this object
|
@@ -41,10 +53,12 @@ module Verquest
|
|
41
53
|
def to_schema
|
42
54
|
{
|
43
55
|
name => {
|
44
|
-
"type" =>
|
45
|
-
"required" =>
|
56
|
+
"type" => type,
|
57
|
+
"required" => required_properties,
|
46
58
|
"properties" => properties.transform_values { |property| property.to_schema[property.name] }
|
47
|
-
}.merge(schema_options)
|
59
|
+
}.merge(schema_options).tap do |schema|
|
60
|
+
schema["dependentRequired"] = dependent_required_properties if dependent_required_properties.any?
|
61
|
+
end
|
48
62
|
}
|
49
63
|
end
|
50
64
|
|
@@ -55,10 +69,12 @@ module Verquest
|
|
55
69
|
def to_validation_schema(version: nil)
|
56
70
|
{
|
57
71
|
name => {
|
58
|
-
"type" =>
|
59
|
-
"required" =>
|
72
|
+
"type" => type,
|
73
|
+
"required" => required_properties,
|
60
74
|
"properties" => properties.transform_values { |property| property.to_validation_schema(version: version)[property.name] }
|
61
|
-
}.merge(schema_options)
|
75
|
+
}.merge(schema_options).tap do |schema|
|
76
|
+
schema["dependentRequired"] = dependent_required_properties if dependent_required_properties.any?
|
77
|
+
end
|
62
78
|
}
|
63
79
|
end
|
64
80
|
|
@@ -26,12 +26,14 @@ module Verquest
|
|
26
26
|
# @param name [String, Symbol] The name of the property
|
27
27
|
# @param from [Class] The schema class to reference
|
28
28
|
# @param property [Symbol, nil] Optional specific property to reference
|
29
|
+
# @param nullable [Boolean] Whether this property can be null
|
29
30
|
# @param map [String, nil] The mapping path for this property
|
30
|
-
# @param required [Boolean] Whether this property is required
|
31
|
-
def initialize(name:, from:, property: nil, map: nil, required: false)
|
31
|
+
# @param required [Boolean, Array<Symbol>] Whether this property is required, or array of dependency names
|
32
|
+
def initialize(name:, from:, property: nil, nullable: false, map: nil, required: false)
|
32
33
|
@name = name.to_s
|
33
34
|
@from = from
|
34
35
|
@property = property
|
36
|
+
@nullable = nullable
|
35
37
|
@map = map
|
36
38
|
@required = required
|
37
39
|
end
|
@@ -40,9 +42,20 @@ module Verquest
|
|
40
42
|
#
|
41
43
|
# @return [Hash] The schema definition with a $ref pointer
|
42
44
|
def to_schema
|
43
|
-
|
44
|
-
|
45
|
-
|
45
|
+
if nullable
|
46
|
+
{
|
47
|
+
name => {
|
48
|
+
"oneOf" => [
|
49
|
+
{"$ref" => from.to_ref(property: property)},
|
50
|
+
{"type" => "null"}
|
51
|
+
]
|
52
|
+
}
|
53
|
+
}
|
54
|
+
else
|
55
|
+
{
|
56
|
+
name => {"$ref" => from.to_ref(property: property)}
|
57
|
+
}
|
58
|
+
end
|
46
59
|
end
|
47
60
|
|
48
61
|
# Generate validation schema for this reference property
|
@@ -50,8 +63,14 @@ module Verquest
|
|
50
63
|
# @param version [String, nil] The version to generate validation schema for
|
51
64
|
# @return [Hash] The validation schema for this reference
|
52
65
|
def to_validation_schema(version: nil)
|
66
|
+
schema = from.to_validation_schema(version:, property: property).dup
|
67
|
+
|
68
|
+
if nullable
|
69
|
+
schema["type"] = [schema["type"], "null"] unless schema["type"].include?("null")
|
70
|
+
end
|
71
|
+
|
53
72
|
{
|
54
|
-
name =>
|
73
|
+
name => schema
|
55
74
|
}
|
56
75
|
end
|
57
76
|
|
data/lib/verquest/version.rb
CHANGED
@@ -19,6 +19,8 @@ module Verquest
|
|
19
19
|
# # Get mapping
|
20
20
|
# mapping = version.mapping
|
21
21
|
class Version
|
22
|
+
include HelperMethods::RequiredProperties
|
23
|
+
|
22
24
|
# @!attribute [r] name
|
23
25
|
# @return [String] The name/identifier of the version (e.g., "2023-01")
|
24
26
|
#
|
@@ -102,6 +104,12 @@ module Verquest
|
|
102
104
|
def prepare
|
103
105
|
return if frozen?
|
104
106
|
|
107
|
+
unless schema_options.key?("additionalProperties")
|
108
|
+
schema_options["additionalProperties"] = Verquest.configuration.default_additional_properties
|
109
|
+
end
|
110
|
+
|
111
|
+
schema_options.delete_if { |_, v| v.nil? }
|
112
|
+
|
105
113
|
prepare_schema
|
106
114
|
prepare_validation_schema
|
107
115
|
prepare_mapping
|
@@ -113,11 +121,33 @@ module Verquest
|
|
113
121
|
# Validate the schema against the metaschema
|
114
122
|
#
|
115
123
|
# @return [Boolean] true if the schema is valid, false otherwise
|
124
|
+
def valid_schema?
|
125
|
+
JSONSchemer.valid_schema?(
|
126
|
+
validation_schema,
|
127
|
+
meta_schema: Verquest.configuration.json_schema_uri
|
128
|
+
)
|
129
|
+
end
|
130
|
+
|
131
|
+
# Validate the schema against the metaschema and return detailed errors
|
132
|
+
#
|
133
|
+
# This method validates the schema against the configured JSON Schema metaschema
|
134
|
+
# and returns detailed validation errors if any are found. It uses the JSONSchemer
|
135
|
+
# library with the schema version specified in the configuration.
|
136
|
+
#
|
137
|
+
# @return [Array<Hash>] An array of validation error details, empty if schema is valid
|
138
|
+
# @see #valid_schema?
|
116
139
|
def validate_schema
|
117
140
|
JSONSchemer.validate_schema(
|
118
141
|
validation_schema,
|
119
142
|
meta_schema: Verquest.configuration.json_schema_uri
|
120
|
-
)
|
143
|
+
).map do |error|
|
144
|
+
{
|
145
|
+
pointer: error["data_pointer"],
|
146
|
+
type: error["type"],
|
147
|
+
message: error["error"],
|
148
|
+
details: error["details"]
|
149
|
+
}
|
150
|
+
end
|
121
151
|
end
|
122
152
|
|
123
153
|
# Validate request parameters against the version's validation schema
|
@@ -175,9 +205,11 @@ module Verquest
|
|
175
205
|
@schema = {
|
176
206
|
"type" => "object",
|
177
207
|
"description" => description,
|
178
|
-
"required" =>
|
208
|
+
"required" => required_properties,
|
179
209
|
"properties" => properties.transform_values { |property| property.to_schema[property.name] }
|
180
|
-
}.merge(schema_options).
|
210
|
+
}.merge(schema_options).tap do |schema|
|
211
|
+
schema["dependentRequired"] = dependent_required_properties if dependent_required_properties.any?
|
212
|
+
end.freeze
|
181
213
|
end
|
182
214
|
|
183
215
|
# Generates the validation schema for this version
|
@@ -190,9 +222,11 @@ module Verquest
|
|
190
222
|
@validation_schema = {
|
191
223
|
"type" => "object",
|
192
224
|
"description" => description,
|
193
|
-
"required" =>
|
225
|
+
"required" => required_properties,
|
194
226
|
"properties" => properties.transform_values { |property| property.to_validation_schema(version: name)[property.name] }
|
195
|
-
}.merge(schema_options).
|
227
|
+
}.merge(schema_options).tap do |schema|
|
228
|
+
schema["dependentRequired"] = dependent_required_properties if dependent_required_properties.any?
|
229
|
+
end.freeze
|
196
230
|
end
|
197
231
|
|
198
232
|
# Prepares the parameter mapping for this version
|
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,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: verquest
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Petr Hlavicka
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: zeitwerk
|
@@ -62,6 +61,7 @@ files:
|
|
62
61
|
- lib/verquest/base/public_class_methods.rb
|
63
62
|
- lib/verquest/configuration.rb
|
64
63
|
- lib/verquest/gem_version.rb
|
64
|
+
- lib/verquest/helper_methods/required_properties.rb
|
65
65
|
- lib/verquest/properties.rb
|
66
66
|
- lib/verquest/properties/array.rb
|
67
67
|
- lib/verquest/properties/base.rb
|
@@ -81,7 +81,6 @@ metadata:
|
|
81
81
|
homepage_uri: https://github.com/CiTroNaK/verquest
|
82
82
|
source_code_uri: https://github.com/CiTroNaK/verquest
|
83
83
|
changelog_uri: https://github.com/CiTroNaK/verquest/blob/main/CHANGELOG.md
|
84
|
-
post_install_message:
|
85
84
|
rdoc_options: []
|
86
85
|
require_paths:
|
87
86
|
- lib
|
@@ -96,8 +95,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
96
95
|
- !ruby/object:Gem::Version
|
97
96
|
version: '0'
|
98
97
|
requirements: []
|
99
|
-
rubygems_version: 3.
|
100
|
-
signing_key:
|
98
|
+
rubygems_version: 3.6.9
|
101
99
|
specification_version: 4
|
102
100
|
summary: Verquest is a Ruby gem that offers an elegant solution for versioning API
|
103
101
|
requests
|