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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0ccc6bfd2d16a987d2ed8ae6c04a9fbfd72a0ed58f00699221193ef76e764cd0
4
- data.tar.gz: '0026964f349c5d17f41b18cd7f32431519c46f1530fe92a535488abcd202a86e'
3
+ metadata.gz: 725838444df6f3861ab842c88b430aa7bbc81023637595b4a0a8a909df5a2fbe
4
+ data.tar.gz: 88041a5f8bc888f90894975b5fb371ac8184877bb75b74e2e099ce57997b331a
5
5
  SHA512:
6
- metadata.gz: dba0dc768c80d2f51cfb74a9c93483218c8d3f28fffffe01b41d250e78f0d2e504ba64af6ea2dd723de22640170b2151f860424bcdc5ad05b29d24fa694a5081
7
- data.tar.gz: 662c43fb095decd144c50afece842467ff96705bbe4a51f27a347a2fdf6041a0bc98895274f280a1441ca88f445a1820d519fc280f9304bc26826f44ea9fcb23
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.2"
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.validate_schema(version: "2025-06") # => true/false
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 = :draft6 # default
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
- attr_reader :current_version, :version_resolver
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Verquest
4
- GEM_VERSION = "0.3.0"
4
+ GEM_VERSION = "0.4.0"
5
5
  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,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 schema_options [Hash] Additional JSON schema options for this property
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 allowed field types
19
+ # List of default field types
19
20
  # @return [Array<Symbol>]
20
- ALLOWED_TYPES = %w[string number integer boolean].freeze
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, must be one of ALLOWED_TYPES
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 #{ALLOWED_TYPES.join(", ")}" unless ALLOWED_TYPES.include?(type.to_s)
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
@@ -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.3.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-25 00:00:00.000000000 Z
11
+ date: 2025-06-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: zeitwerk