Class: Verquest::Version
- Inherits:
-
Object
- Object
- Verquest::Version
- Includes:
- HelperMethods::RequiredProperties
- Defined in:
- lib/verquest/version.rb
Overview
Represents a specific version of an API request schema
The Version class manages the properties, schema generation, and mapping for a specific version of an API request. It holds the collection of properties that define the request structure and handles transforming between different property naming conventions.
Instance Attribute Summary collapse
-
#description ⇒ String
Description of this version.
-
#external_mapping ⇒ Object
readonly
Returns the value of attribute external_mapping.
-
#mapping ⇒ Hash
readonly
The mapping from schema property paths to internal paths.
-
#name ⇒ String
readonly
The name/identifier of the version (e.g., “2023-01”).
-
#properties ⇒ Hash<Symbol, Verquest::Properties::Base>
readonly
The properties that define the version’s schema.
-
#schema ⇒ Hash
readonly
The generated JSON schema for this version.
-
#schema_options ⇒ Hash
Additional JSON schema options for this version.
-
#transformer ⇒ Verquest::Transformer
readonly
The transformer that applies the mapping.
-
#validation_schema ⇒ Hash
readonly
The schema used for request validation.
Instance Method Summary collapse
-
#add(property) ⇒ Verquest::Properties::Base
Add a property to this version.
-
#combination? ⇒ Boolean
Check if this version is a combination schema (root-level oneOf).
-
#combination_discriminator ⇒ String?
private
Returns the discriminator property name for combination schemas.
-
#copy_from(version, exclude_properties: []) ⇒ void
Copy properties from another version.
-
#has?(property_name) ⇒ Boolean
Check if this version has a property with the given name.
-
#has_multiple_nested_one_of? ⇒ Boolean
Check if this version has multiple nested oneOf properties.
-
#has_nested_one_of? ⇒ Boolean
Check if this version has a nested oneOf property (oneOf with a name).
-
#initialize(name:) ⇒ Version
constructor
Initialize a new Version instance.
-
#invert_multiple_one_of_mapping ⇒ Hash
private
Inverts mapping for multiple nested oneOf.
-
#invert_single_one_of_mapping ⇒ Hash
private
Inverts mapping for single nested oneOf.
-
#map_params(params) ⇒ Hash
Map request parameters to internal representation using the transformer.
-
#mapping_for(property) ⇒ Hash
Get the mapping for a specific property.
-
#nested_one_of_count ⇒ Integer
Returns the count of nested oneOf properties (computed once and cached).
-
#prepare ⇒ void
Prepare this version by generating schema and creating transformer.
-
#prepare_combination_external_mapping ⇒ Hash
private
Prepares the inverted parameter mapping for combination schemas.
-
#prepare_combination_mapping ⇒ Hash
private
Prepares the parameter mapping for combination schemas (oneOf).
-
#prepare_combination_schema ⇒ Hash
private
Generates the JSON schema for combination schemas (oneOf at root level).
-
#prepare_combination_validation_schema ⇒ Hash
private
Generates the validation schema for combination schemas (oneOf at root level).
-
#prepare_external_mapping ⇒ Hash
private
Prepares the inverted parameter mapping for this version.
-
#prepare_flat_mapping(properties_list) ⇒ void
private
Prepares flat mapping for versions without nested oneOf.
-
#prepare_mapping ⇒ Hash
private
Prepares the parameter mapping for this version.
-
#prepare_multiple_nested_one_of_mapping(one_of_properties, regular_properties) ⇒ void
private
Prepares mapping for versions with multiple nested oneOf properties.
-
#prepare_schema ⇒ Hash
private
Generates the JSON schema for this version.
-
#prepare_single_nested_one_of_mapping(one_of_property, regular_properties) ⇒ void
private
Prepares mapping for versions with a single nested oneOf property (legacy format).
-
#prepare_validation_schema ⇒ Hash
private
Generates the validation schema for this version.
-
#remove(property_name) ⇒ Verquest::Properties::Base
Remove a property from this version by name.
-
#valid_schema? ⇒ Boolean
Validate the schema against the metaschema.
-
#validate_params(params:) ⇒ Array<Hash>
Validate request parameters against the version’s validation schema.
-
#validate_schema ⇒ Array<Hash>
Validate the schema against the metaschema and return detailed errors.
Methods included from HelperMethods::RequiredProperties
#dependent_required_properties, #required_properties
Constructor Details
#initialize(name:) ⇒ Version
Initialize a new Version instance
57 58 59 60 61 |
# File 'lib/verquest/version.rb', line 57 def initialize(name:) @name = name.to_s = {} @properties = {} end |
Instance Attribute Details
#description ⇒ String
Returns Description of this version.
51 |
# File 'lib/verquest/version.rb', line 51 attr_accessor :schema_options, :description |
#external_mapping ⇒ Object (readonly)
Returns the value of attribute external_mapping.
44 |
# File 'lib/verquest/version.rb', line 44 attr_reader :name, :properties, :schema, :validation_schema, :mapping, :transformer, :external_mapping |
#mapping ⇒ Hash (readonly)
Returns The mapping from schema property paths to internal paths.
44 |
# File 'lib/verquest/version.rb', line 44 attr_reader :name, :properties, :schema, :validation_schema, :mapping, :transformer, :external_mapping |
#name ⇒ String (readonly)
Returns The name/identifier of the version (e.g., “2023-01”).
44 45 46 |
# File 'lib/verquest/version.rb', line 44 def name @name end |
#properties ⇒ Hash<Symbol, Verquest::Properties::Base> (readonly)
Returns The properties that define the version’s schema.
44 |
# File 'lib/verquest/version.rb', line 44 attr_reader :name, :properties, :schema, :validation_schema, :mapping, :transformer, :external_mapping |
#schema ⇒ Hash (readonly)
Returns The generated JSON schema for this version.
44 |
# File 'lib/verquest/version.rb', line 44 attr_reader :name, :properties, :schema, :validation_schema, :mapping, :transformer, :external_mapping |
#schema_options ⇒ Hash
Returns Additional JSON schema options for this version.
51 52 53 |
# File 'lib/verquest/version.rb', line 51 def end |
#transformer ⇒ Verquest::Transformer (readonly)
Returns The transformer that applies the mapping.
44 |
# File 'lib/verquest/version.rb', line 44 attr_reader :name, :properties, :schema, :validation_schema, :mapping, :transformer, :external_mapping |
#validation_schema ⇒ Hash (readonly)
Returns The schema used for request validation.
44 |
# File 'lib/verquest/version.rb', line 44 attr_reader :name, :properties, :schema, :validation_schema, :mapping, :transformer, :external_mapping |
Instance Method Details
#add(property) ⇒ Verquest::Properties::Base
Add a property to this version
67 68 69 |
# File 'lib/verquest/version.rb', line 67 def add(property) properties[property.name] = property end |
#combination? ⇒ Boolean
Check if this version is a combination schema (root-level oneOf)
A combination schema has a single root-level oneOf property (name is nil) where the entire request body matches one of the defined schemas.
213 214 215 216 217 218 219 |
# File 'lib/verquest/version.rb', line 213 def combination? return @_combination if defined?(@_combination) @_combination = properties.values.count == 1 && properties.values.first&.is_a?(Verquest::Properties::OneOf) && properties.values.first.name.nil? end |
#combination_discriminator ⇒ String? (private)
Returns the discriminator property name for combination schemas
489 490 491 492 493 |
# File 'lib/verquest/version.rb', line 489 def combination_discriminator return nil unless combination? properties.values.first.send(:discriminator) end |
#copy_from(version, exclude_properties: []) ⇒ void
This method returns an undefined value.
Copy properties from another version
94 95 96 97 98 99 100 101 102 |
# File 'lib/verquest/version.rb', line 94 def copy_from(version, exclude_properties: []) raise ArgumentError, "Expected a Verquest::Version instance" unless version.is_a?(Version) version.properties.values.each do |property| next if exclude_properties.include?(property.name.to_sym) add(property) end end |
#has?(property_name) ⇒ Boolean
Check if this version has a property with the given name
84 85 86 |
# File 'lib/verquest/version.rb', line 84 def has?(property_name) properties.key?(property_name.to_s) end |
#has_multiple_nested_one_of? ⇒ Boolean
Check if this version has multiple nested oneOf properties
231 232 233 |
# File 'lib/verquest/version.rb', line 231 def has_multiple_nested_one_of? nested_one_of_count > 1 end |
#has_nested_one_of? ⇒ Boolean
Check if this version has a nested oneOf property (oneOf with a name)
224 225 226 |
# File 'lib/verquest/version.rb', line 224 def has_nested_one_of? nested_one_of_count > 0 end |
#invert_multiple_one_of_mapping ⇒ Hash (private)
Inverts mapping for multiple nested oneOf
434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 |
# File 'lib/verquest/version.rb', line 434 def invert_multiple_one_of_mapping result = {} mapping.each do |key, value| if key == "_oneOfs" result["_oneOfs"] = value.map do |one_of_mapping| one_of_mapping.each_with_object({}) do |(k, v), inverted| inverted[k] = if k.start_with?("_") v else v.invert end end end elsif key.start_with?("_") result[key] = value else result[value] = key # Invert base properties end end result.freeze end |
#invert_single_one_of_mapping ⇒ Hash (private)
Inverts mapping for single nested oneOf
421 422 423 424 425 426 427 428 429 |
# File 'lib/verquest/version.rb', line 421 def invert_single_one_of_mapping mapping.each_with_object({}) do |(key, value), result| result[key] = if key.start_with?("_") value else value.invert end end.freeze end |
#map_params(params) ⇒ Hash
Map request parameters to internal representation using the transformer
203 204 205 |
# File 'lib/verquest/version.rb', line 203 def map_params(params) transformer.call(params) end |
#mapping_for(property) ⇒ Hash
Get the mapping for a specific property
191 192 193 194 195 196 197 |
# File 'lib/verquest/version.rb', line 191 def mapping_for(property) raise PropertyNotFoundError.new("Property '#{property}' is not defined on '#{name}'") unless has?(property) {}.tap do |mapping| properties[property.to_s].mapping(key_prefix: [], value_prefix: [], mapping: mapping, version: name) end end |
#nested_one_of_count ⇒ Integer
Returns the count of nested oneOf properties (computed once and cached)
238 239 240 241 242 243 244 |
# File 'lib/verquest/version.rb', line 238 def nested_one_of_count return @_nested_one_of_count if defined?(@_nested_one_of_count) @_nested_one_of_count = properties.values.count do |p| p.is_a?(Verquest::Properties::OneOf) && !p.name.nil? end end |
#prepare ⇒ void
This method returns an undefined value.
Prepare this version by generating schema and creating transformer
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
# File 'lib/verquest/version.rb', line 107 def prepare return if frozen? unless .key?("additionalProperties") ["additionalProperties"] = Verquest.configuration.default_additional_properties end .delete_if { |_, v| v.nil? } if combination? prepare_combination_schema prepare_combination_validation_schema prepare_combination_mapping prepare_combination_external_mapping @transformer = Transformer.new(mapping: mapping, discriminator: combination_discriminator) else prepare_schema prepare_validation_schema prepare_mapping prepare_external_mapping @transformer = Transformer.new(mapping: mapping) end freeze end |
#prepare_combination_external_mapping ⇒ Hash (private)
Prepares the inverted parameter mapping for combination schemas
For combination schemas, inverts each discriminator value’s mapping. Skips metadata keys that are not variant mappings.
475 476 477 478 479 480 481 482 483 484 |
# File 'lib/verquest/version.rb', line 475 def prepare_combination_external_mapping @external_mapping = mapping.each_with_object({}) do |(key, value), result| # Skip metadata keys, only invert variant mapping hashes result[key] = if key.start_with?("_") value else value.invert end end.freeze end |
#prepare_combination_mapping ⇒ Hash (private)
Prepares the parameter mapping for combination schemas (oneOf)
For combination schemas, the mapping is keyed by the discriminator value so the transformer can select the appropriate mapping based on the input.
464 465 466 467 |
# File 'lib/verquest/version.rb', line 464 def prepare_combination_mapping @mapping = {} properties.values.first.mapping(key_prefix: [], value_prefix: [], mapping: @mapping, version: name) end |
#prepare_combination_schema ⇒ Hash (private)
Generates the JSON schema for combination schemas (oneOf at root level)
For combination schemas, the schema is delegated directly to the oneOf property since it represents the entire request structure.
272 273 274 |
# File 'lib/verquest/version.rb', line 272 def prepare_combination_schema @schema = properties.values.first.to_schema end |
#prepare_combination_validation_schema ⇒ Hash (private)
Generates the validation schema for combination schemas (oneOf at root level)
For combination schemas, the validation schema is delegated directly to the oneOf property, which includes inline schema definitions for each option.
299 300 301 |
# File 'lib/verquest/version.rb', line 299 def prepare_combination_validation_schema @validation_schema = properties.values.first.to_validation_schema(version: name) end |
#prepare_external_mapping ⇒ Hash (private)
Prepares the inverted parameter mapping for this version
Inverts the standard mapping to create a reverse lookup from internal attribute names back to external parameter names. This is useful when transforming internal data back to the external API representation.
For nested oneOf schemas, inverts each discriminator value’s mapping. Skips metadata keys that are not variant mappings.
408 409 410 411 412 413 414 415 416 |
# File 'lib/verquest/version.rb', line 408 def prepare_external_mapping @external_mapping = if has_multiple_nested_one_of? invert_multiple_one_of_mapping elsif has_nested_one_of? invert_single_one_of_mapping else mapping.invert.freeze end end |
#prepare_flat_mapping(properties_list) ⇒ void (private)
This method returns an undefined value.
Prepares flat mapping for versions without nested oneOf
384 385 386 387 388 389 390 391 392 393 394 |
# File 'lib/verquest/version.rb', line 384 def prepare_flat_mapping(properties_list) @mapping = properties_list.each_with_object({}) do |property, mapping| property.mapping(key_prefix: [], value_prefix: [], mapping: mapping, version: name) end seen = Set.new duplicates = mapping.values.select { |v| !seen.add?(v) } if duplicates.any? raise MappingError.new("Mapping must be unique. Found duplicates in version '#{name}': #{duplicates.uniq.join(", ")}") end end |
#prepare_mapping ⇒ Hash (private)
Prepares the parameter mapping for this version
Collects mappings from all properties in this version and checks for duplicate mappings, which would cause conflicts during transformation.
When nested oneOf properties are present, the mapping includes a _oneOfs array containing each oneOf’s metadata and variant mappings, plus base properties.
313 314 315 316 317 318 319 320 321 322 323 324 325 |
# File 'lib/verquest/version.rb', line 313 def prepare_mapping # Separate oneOf properties from regular properties one_of_properties = properties.values.select { |p| p.is_a?(Verquest::Properties::OneOf) } regular_properties = properties.values.reject { |p| p.is_a?(Verquest::Properties::OneOf) } if one_of_properties.size == 1 prepare_single_nested_one_of_mapping(one_of_properties.first, regular_properties) elsif one_of_properties.size > 1 prepare_multiple_nested_one_of_mapping(one_of_properties, regular_properties) else prepare_flat_mapping(regular_properties) end end |
#prepare_multiple_nested_one_of_mapping(one_of_properties, regular_properties) ⇒ void (private)
This method returns an undefined value.
Prepares mapping for versions with multiple nested oneOf properties
363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 |
# File 'lib/verquest/version.rb', line 363 def prepare_multiple_nested_one_of_mapping(one_of_properties, regular_properties) @mapping = {} # Collect regular property mappings at root level regular_properties.each do |property| property.mapping(key_prefix: [], value_prefix: [], mapping: @mapping, version: name) end # Collect each oneOf's mapping into _oneOfs array @mapping["_oneOfs"] = one_of_properties.map do |one_of_property| one_of_mapping = {} one_of_property.mapping(key_prefix: [], value_prefix: [], mapping: one_of_mapping, version: name) one_of_mapping end end |
#prepare_schema ⇒ Hash (private)
Generates the JSON schema for this version
Creates a schema object with type, description, required properties, and property definitions based on the properties in this version. The schema is frozen to prevent modification after preparation.
255 256 257 258 259 260 261 262 263 264 |
# File 'lib/verquest/version.rb', line 255 def prepare_schema @schema = { "type" => "object", "required" => required_properties, "properties" => properties.transform_values { |property| property.to_schema[property.name] } }.merge().tap do |schema| schema["dependentRequired"] = dependent_required_properties if dependent_required_properties.any? schema["description"] = description if description end.freeze end |
#prepare_single_nested_one_of_mapping(one_of_property, regular_properties) ⇒ void (private)
This method returns an undefined value.
Prepares mapping for versions with a single nested oneOf property (legacy format)
332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 |
# File 'lib/verquest/version.rb', line 332 def prepare_single_nested_one_of_mapping(one_of_property, regular_properties) # Collect regular property mappings regular_mapping = {} regular_properties.each do |property| property.mapping(key_prefix: [], value_prefix: [], mapping: regular_mapping, version: name) end # Collect oneOf property mappings one_of_mapping = {} one_of_property.mapping(key_prefix: [], value_prefix: [], mapping: one_of_mapping, version: name) # Merge regular mappings into each oneOf variant @mapping = {} # Preserve metadata keys %w[_discriminator _variant_schemas _variant_path _nullable _nullable_path _nullable_target_path].each do || @mapping[] = one_of_mapping[] if one_of_mapping.key?() end one_of_mapping.each do |discriminator_value, variant_mapping| next if discriminator_value.start_with?("_") # Skip metadata keys @mapping[discriminator_value] = regular_mapping.merge(variant_mapping) end end |
#prepare_validation_schema ⇒ Hash (private)
Generates the validation schema for this version
Similar to prepare_schema but specifically for validation purposes. The validation schema will include all referenced components and properties.
282 283 284 285 286 287 288 289 290 291 |
# File 'lib/verquest/version.rb', line 282 def prepare_validation_schema @validation_schema = { "type" => "object", "required" => required_properties, "properties" => properties.transform_values { |property| property.to_validation_schema(version: name)[property.name] } }.merge().tap do |schema| schema["dependentRequired"] = dependent_required_properties if dependent_required_properties.any? schema["description"] = description if description end.freeze end |
#remove(property_name) ⇒ Verquest::Properties::Base
Remove a property from this version by name
76 77 78 |
# File 'lib/verquest/version.rb', line 76 def remove(property_name) properties.delete(property_name.to_s) || raise(PropertyNotFoundError.new("Property '#{property_name}' is not defined on '#{name}'")) end |
#valid_schema? ⇒ Boolean
Validate the schema against the metaschema
136 137 138 139 140 141 |
# File 'lib/verquest/version.rb', line 136 def valid_schema? JSONSchemer.valid_schema?( validation_schema, meta_schema: Verquest.configuration.json_schema_uri ) end |
#validate_params(params:) ⇒ Array<Hash>
Validate request parameters against the version’s validation schema
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/verquest/version.rb', line 169 def validate_params(params:) schemer = JSONSchemer.schema( validation_schema, meta_schema: Verquest.configuration.json_schema_uri, insert_property_defaults: Verquest.configuration.insert_property_defaults ) schemer.validate(params).map do |error| { pointer: error["data_pointer"], type: error["type"], message: error["error"], details: error["details"] } end end |
#validate_schema ⇒ Array<Hash>
Validate the schema against the metaschema and return detailed errors
This method validates the schema against the configured JSON Schema metaschema and returns detailed validation errors if any are found. It uses the JSONSchemer library with the schema version specified in the configuration.
151 152 153 154 155 156 157 158 159 160 161 162 163 |
# File 'lib/verquest/version.rb', line 151 def validate_schema JSONSchemer.validate_schema( validation_schema, meta_schema: Verquest.configuration.json_schema_uri ).map do |error| { pointer: error["data_pointer"], type: error["type"], message: error["error"], details: error["details"] } end end |