verquest 0.1.0 → 0.2.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/.yardopts +8 -0
- data/CHANGELOG.md +2 -0
- data/README.md +416 -13
- data/lib/verquest/base/helper_class_methods.rb +37 -0
- data/lib/verquest/base/private_class_methods.rb +247 -0
- data/lib/verquest/base/public_class_methods.rb +108 -0
- data/lib/verquest/base.rb +38 -0
- data/lib/verquest/configuration.rb +73 -0
- data/lib/verquest/gem_version.rb +5 -0
- data/lib/verquest/properties/array.rb +63 -0
- data/lib/verquest/properties/base.rb +104 -0
- data/lib/verquest/properties/collection.rb +147 -0
- data/lib/verquest/properties/field.rb +65 -0
- data/lib/verquest/properties/object.rb +83 -0
- data/lib/verquest/properties/reference.rb +92 -0
- data/lib/verquest/properties.rb +47 -0
- data/lib/verquest/result.rb +75 -0
- data/lib/verquest/transformer.rb +179 -0
- data/lib/verquest/version.rb +208 -1
- data/lib/verquest/version_resolver.rb +42 -0
- data/lib/verquest/versions.rb +65 -0
- data/lib/verquest.rb +96 -3
- metadata +40 -6
@@ -0,0 +1,247 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Verquest
|
4
|
+
# Private class methods for Verquest::Base
|
5
|
+
#
|
6
|
+
# This module contains internal class methods used by the versioning system
|
7
|
+
# that are not intended for direct use by client code.
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
module Base::PrivateClassMethods
|
11
|
+
# Resolves the version to use, either from the provided version,
|
12
|
+
# configuration's current_version, or raises an error if none is available
|
13
|
+
#
|
14
|
+
# @param version [String, nil] The specific version to resolve
|
15
|
+
# @return [Verquest::Version] The resolved version object
|
16
|
+
# @raise [ArgumentError] If no version is provided and no current_version is configured
|
17
|
+
def resolve(version)
|
18
|
+
if version.nil? && Verquest.configuration.current_version
|
19
|
+
version = instance_exec(&Verquest.configuration.current_version)
|
20
|
+
elsif version.nil?
|
21
|
+
raise ArgumentError, "Version must be provided or set by Verquest.configuration.current_version"
|
22
|
+
end
|
23
|
+
|
24
|
+
versions.resolve(version)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
# @return [Verquest::Version, Verquest::Properties::Object] The current scope being defined
|
30
|
+
attr_reader :current_scope
|
31
|
+
|
32
|
+
# @return [Hash] Default options for property definitions
|
33
|
+
# Default options used when using teh with_options method.
|
34
|
+
attr_reader :default_options
|
35
|
+
|
36
|
+
# Returns the versions container, initializing it if needed
|
37
|
+
#
|
38
|
+
# @return [Verquest::Versions] The versions container
|
39
|
+
def versions
|
40
|
+
@versions ||= Versions.new
|
41
|
+
end
|
42
|
+
|
43
|
+
# Defines a new version with optional inheritance from another version
|
44
|
+
#
|
45
|
+
# @param name [String] The name/identifier of the version
|
46
|
+
# @param inherit [Boolean, String] Whether to inherit from current scope or specific version name
|
47
|
+
# @param exclude_properties [Array<Symbol>] Properties to exclude when inheriting
|
48
|
+
# @yield Block defining the version's structure and properties
|
49
|
+
# @return [void]
|
50
|
+
def version(name, inherit: true, exclude_properties: [], &block)
|
51
|
+
version = Version.new(name:)
|
52
|
+
versions.add(version)
|
53
|
+
|
54
|
+
if inherit && @current_scope
|
55
|
+
version.copy_from(@current_scope, exclude_properties:)
|
56
|
+
elsif inherit.is_a?(String)
|
57
|
+
inherit_version = versions.resolve(inherit)
|
58
|
+
version.copy_from(inherit_version, exclude_properties:)
|
59
|
+
end
|
60
|
+
|
61
|
+
@default_options = {}
|
62
|
+
@current_scope = version
|
63
|
+
|
64
|
+
instance_exec(&block)
|
65
|
+
ensure
|
66
|
+
version.description ||= versions.description
|
67
|
+
version.schema_options = versions.schema_options.merge(version.schema_options)
|
68
|
+
version.prepare
|
69
|
+
end
|
70
|
+
|
71
|
+
# Sets the description for the current version scope or globally
|
72
|
+
#
|
73
|
+
# @param text [String] The description text
|
74
|
+
# @return [void]
|
75
|
+
# @raise [RuntimeError] If called outside of a version scope
|
76
|
+
def description(text)
|
77
|
+
if current_scope.nil?
|
78
|
+
versions.description = text
|
79
|
+
elsif current_scope.is_a?(Version)
|
80
|
+
current_scope.description = text
|
81
|
+
else
|
82
|
+
raise "Description can only be set within a version scope or globally"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Sets additional schema options for the current version scope or globally
|
87
|
+
#
|
88
|
+
# @param schema_options [Hash] The schema options to set
|
89
|
+
# @return [void]
|
90
|
+
# @raise [RuntimeError] If called outside of a version scope
|
91
|
+
def schema_options(**schema_options)
|
92
|
+
camelize(schema_options)
|
93
|
+
|
94
|
+
if current_scope.nil?
|
95
|
+
versions.schema_options = schema_options
|
96
|
+
elsif current_scope.is_a?(Version)
|
97
|
+
current_scope.schema_options = schema_options
|
98
|
+
else
|
99
|
+
raise "Additional properties can only be set within a version scope or globally"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Executes the given block with the specified options, temporarily overriding
|
104
|
+
# the default options for the duration of the block
|
105
|
+
#
|
106
|
+
# @param options [Hash] The options to set during the block execution
|
107
|
+
# @yield Block to be executed with the temporary options
|
108
|
+
# @return [void]
|
109
|
+
def with_options(**options, &block)
|
110
|
+
camelize(options)
|
111
|
+
|
112
|
+
original_options = default_options
|
113
|
+
@default_options = options.except(:map)
|
114
|
+
|
115
|
+
instance_exec(&block)
|
116
|
+
ensure
|
117
|
+
@default_options = original_options
|
118
|
+
end
|
119
|
+
|
120
|
+
# Defines a new field for the current version scope
|
121
|
+
#
|
122
|
+
# @param name [Symbol] The name of the field
|
123
|
+
# @param type [Symbol] The data type of the field
|
124
|
+
# @param map [String, nil] An optional mapping to another field
|
125
|
+
# @param required [Boolean] Whether the field is required
|
126
|
+
# @param schema_options [Hash] Additional schema options for the field
|
127
|
+
# @return [void]
|
128
|
+
def field(name, type: nil, map: nil, required: false, **schema_options)
|
129
|
+
camelize(schema_options)
|
130
|
+
|
131
|
+
type = default_options.fetch(:type, type)
|
132
|
+
required = default_options.fetch(:required, required)
|
133
|
+
schema_options = default_options.except(:type, :required).merge(schema_options)
|
134
|
+
|
135
|
+
field = Properties::Field.new(name:, type:, map:, required:, **schema_options)
|
136
|
+
current_scope.add(field)
|
137
|
+
end
|
138
|
+
|
139
|
+
# Defines a new object for the current version scope
|
140
|
+
#
|
141
|
+
# @param name [Symbol] The name of the object
|
142
|
+
# @param map [String, nil] An optional mapping to another object
|
143
|
+
# @param required [Boolean] Whether the object is required
|
144
|
+
# @param schema_options [Hash] Additional schema options for the object
|
145
|
+
# @yield Block executed in the context of the new object definition
|
146
|
+
# @return [void]
|
147
|
+
def object(name, map: nil, required: false, **schema_options, &block)
|
148
|
+
unless block_given?
|
149
|
+
raise ArgumentError, "a block must be given to define the object"
|
150
|
+
end
|
151
|
+
|
152
|
+
camelize(schema_options)
|
153
|
+
required = default_options.fetch(:required, required)
|
154
|
+
schema_options = default_options.except(:type, :required).merge(schema_options)
|
155
|
+
|
156
|
+
object = Properties::Object.new(name:, map:, required:, **schema_options)
|
157
|
+
current_scope.add(object)
|
158
|
+
|
159
|
+
if block_given?
|
160
|
+
previous_scope = current_scope
|
161
|
+
@current_scope = object
|
162
|
+
|
163
|
+
instance_exec(&block)
|
164
|
+
end
|
165
|
+
ensure
|
166
|
+
@current_scope = previous_scope if block_given?
|
167
|
+
end
|
168
|
+
|
169
|
+
# Defines a new collection for the current version scope
|
170
|
+
#
|
171
|
+
# @param name [Symbol] The name of the collection
|
172
|
+
# @param item [Class, nil] The item type in the collection
|
173
|
+
# @param required [Boolean] Whether the collection is required
|
174
|
+
# @param map [String, nil] An optional mapping to another collection
|
175
|
+
# @param schema_options [Hash] Additional schema options for the collection
|
176
|
+
# @yield Block executed in the context of the new collection definition
|
177
|
+
# @return [void]
|
178
|
+
def collection(name, item: nil, required: false, map: nil, **schema_options, &block)
|
179
|
+
if item.nil? && !block_given?
|
180
|
+
raise ArgumentError, "item must be provided or a block must be given to define the collection"
|
181
|
+
elsif !item.nil? && !block_given? && !(item <= Verquest::Base)
|
182
|
+
raise ArgumentError, "item must be a child of Verquest::Base class or nil" unless type.is_a?(Verquest::Properties::Base)
|
183
|
+
end
|
184
|
+
|
185
|
+
camelize(schema_options)
|
186
|
+
required = default_options.fetch(:required, required)
|
187
|
+
schema_options = default_options.except(:required).merge(schema_options)
|
188
|
+
|
189
|
+
collection = Properties::Collection.new(name:, item:, required:, map:, **schema_options)
|
190
|
+
current_scope.add(collection)
|
191
|
+
|
192
|
+
if block_given?
|
193
|
+
previous_scope = current_scope
|
194
|
+
@current_scope = collection
|
195
|
+
|
196
|
+
instance_exec(&block)
|
197
|
+
end
|
198
|
+
ensure
|
199
|
+
@current_scope = previous_scope if block_given?
|
200
|
+
end
|
201
|
+
|
202
|
+
# Defines a new reference for the current version scope
|
203
|
+
#
|
204
|
+
# @param name [Symbol] The name of the reference
|
205
|
+
# @param from [Verquest::Base] The source of the reference
|
206
|
+
# @param property [Symbol, nil] An optional specific property to reference
|
207
|
+
# @param map [String, nil] An optional mapping to another reference
|
208
|
+
# @param required [Boolean] Whether the reference is required
|
209
|
+
# @return [void]
|
210
|
+
def reference(name, from:, property: nil, map: nil, required: false)
|
211
|
+
required = default_options.fetch(:required, required)
|
212
|
+
|
213
|
+
reference = Properties::Reference.new(name:, from:, property:, map:, required:)
|
214
|
+
current_scope.add(reference)
|
215
|
+
end
|
216
|
+
|
217
|
+
# Defines a new array property for the current version scope
|
218
|
+
#
|
219
|
+
# @param name [Symbol] The name of the array property
|
220
|
+
# @param type [String] The data type of the array elements
|
221
|
+
# @param required [Boolean] Whether the array property is required
|
222
|
+
# @param map [String, nil] An optional mapping to another array property
|
223
|
+
# @param schema_options [Hash] Additional schema options for the array property
|
224
|
+
# @return [void]
|
225
|
+
def array(name, type:, required: false, map: nil, **schema_options)
|
226
|
+
camelize(schema_options)
|
227
|
+
|
228
|
+
type = default_options.fetch(:type, type)
|
229
|
+
required = default_options.fetch(:required, required)
|
230
|
+
schema_options = default_options.except(:type, :required).merge(schema_options)
|
231
|
+
|
232
|
+
array = Properties::Array.new(name:, type:, required:, map:, **schema_options)
|
233
|
+
current_scope.add(array)
|
234
|
+
end
|
235
|
+
|
236
|
+
# Excludes specified properties from the current scope by removing them
|
237
|
+
# from the version's property set
|
238
|
+
#
|
239
|
+
# @param names [Array<Symbol>] The names of the properties to exclude
|
240
|
+
# @return [void]
|
241
|
+
def exclude_properties(*names)
|
242
|
+
names.each do |name|
|
243
|
+
current_scope.remove(name)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Verquest
|
4
|
+
# Public class methods to be included in Verquest::Base
|
5
|
+
#
|
6
|
+
# This module contains class methods that handle parameter mapping, schema generation,
|
7
|
+
# and validation functionality for Verquest API request objects.
|
8
|
+
module Base::PublicClassMethods
|
9
|
+
# Maps incoming parameters to the appropriate structure based on version mapping
|
10
|
+
#
|
11
|
+
# @param params [Hash] The parameters to be mapped
|
12
|
+
# @param version [String, nil] Specific version to use, defaults to configuration setting
|
13
|
+
# @param validate [Boolean, nil] Whether to validate the params, defaults to configuration setting
|
14
|
+
# @param remove_extra_root_keys [Boolean, nil] Whether to remove extra keys at the root level, defaults to configuration setting
|
15
|
+
# @return [Verquest::Result] Success result with mapped params or failure result with validation errors
|
16
|
+
def process(params, version: nil, validate: nil, remove_extra_root_keys: nil)
|
17
|
+
validate = Verquest.configuration.validate_params if validate.nil?
|
18
|
+
remove_extra_root_keys = Verquest.configuration.remove_extra_root_keys if remove_extra_root_keys.nil?
|
19
|
+
|
20
|
+
version_class = resolve(version)
|
21
|
+
|
22
|
+
params = params.dup
|
23
|
+
params = params.to_unsafe_h if params.respond_to?(:to_unsafe_h)
|
24
|
+
params = params.slice(*version_class.properties.keys) if remove_extra_root_keys
|
25
|
+
|
26
|
+
if validate && (validation_result = version_class.validate_params(params: params, component_reference: to_ref, remove_extra_root_keys: remove_extra_root_keys)) && validation_result.any?
|
27
|
+
case Verquest.configuration.validation_error_handling
|
28
|
+
when :raise
|
29
|
+
raise InvalidParamsError.new("Validation failed", errors: validation_result)
|
30
|
+
when :result
|
31
|
+
Result.failure(validation_result)
|
32
|
+
end
|
33
|
+
else
|
34
|
+
mapped_params = version_class.map_params(params)
|
35
|
+
|
36
|
+
case Verquest.configuration.validation_error_handling
|
37
|
+
when :raise
|
38
|
+
mapped_params
|
39
|
+
when :result
|
40
|
+
Result.success(mapped_params)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns the JSON schema for the request
|
46
|
+
#
|
47
|
+
# @param version [String, nil] Specific version to use, defaults to configuration setting
|
48
|
+
# @return [Hash] The JSON schema for the request
|
49
|
+
def to_schema(version: nil)
|
50
|
+
resolve(version).schema
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns the validation JSON schema for the request or a specific property. It contains all schemas from references.
|
54
|
+
#
|
55
|
+
# @param version [String, nil] Specific version to use, defaults to configuration setting
|
56
|
+
# @param property [Symbol, nil] Specific property to retrieve schema for
|
57
|
+
# @return [Hash] The validation schema or property schema
|
58
|
+
def to_validation_schema(version: nil, property: nil)
|
59
|
+
version = resolve(version)
|
60
|
+
|
61
|
+
if property
|
62
|
+
version.validation_schema[:properties][property]
|
63
|
+
else
|
64
|
+
version.validation_schema
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Validates the generated JSON schema structure
|
69
|
+
#
|
70
|
+
# @param version [String, nil] Specific version to use, defaults to configuration setting
|
71
|
+
# @return [Boolean] True if schema is valid
|
72
|
+
def validate_schema(version: nil)
|
73
|
+
resolve(version).validate_schema
|
74
|
+
end
|
75
|
+
|
76
|
+
# Returns the mapping for a specific version or property
|
77
|
+
#
|
78
|
+
# @param version [String, nil] Specific version to use, defaults to configuration setting
|
79
|
+
# @param property [Symbol, nil] Specific property to retrieve mapping for
|
80
|
+
# @return [Hash] The mapping configuration
|
81
|
+
def mapping(version: nil, property: nil)
|
82
|
+
version = resolve(version)
|
83
|
+
|
84
|
+
if property
|
85
|
+
version.mapping_for(property)
|
86
|
+
else
|
87
|
+
version.mapping
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Returns the JSON reference for the request or a specific property
|
92
|
+
#
|
93
|
+
# @param property [Symbol, nil] Specific property to retrieve reference for
|
94
|
+
# @return [String] The JSON reference for the request or property
|
95
|
+
def to_ref(property: nil)
|
96
|
+
base = "#/components/schemas/#{component_name}"
|
97
|
+
|
98
|
+
property ? "#{base}/properties/#{property}" : base
|
99
|
+
end
|
100
|
+
|
101
|
+
# Returns the component name derived from the class name. It is used in JSON schema references.
|
102
|
+
#
|
103
|
+
# @return [String] The component name
|
104
|
+
def component_name
|
105
|
+
name.to_s.split("::", 2).last.tr("::", "")
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Verquest
|
4
|
+
# Base class for API request definition and mapping
|
5
|
+
#
|
6
|
+
# This class is the foundation of the Verquest versioning system. Classes that inherit from Base
|
7
|
+
# can define their request structure using the versioning DSL, including fields, objects,
|
8
|
+
# collections, and references. The Base class handles parameter mapping, schema generation,
|
9
|
+
# and validation based on version specifications.
|
10
|
+
#
|
11
|
+
# @example Define a versioned API request class
|
12
|
+
# class UserCreateRequest < Verquest::Base
|
13
|
+
# description "User Create Request"
|
14
|
+
# schema_options additional_properties: false
|
15
|
+
#
|
16
|
+
# version "2025-06" do
|
17
|
+
# field :email, type: :string, required: true, format: "email"
|
18
|
+
# field :name, type: :string
|
19
|
+
#
|
20
|
+
# object :address do
|
21
|
+
# field :street, type: :string, map: "/address_street"
|
22
|
+
# field :city, type: :string, required: true, map: "/address_city"
|
23
|
+
# field :zip_code, type: :string, map: "/address_zip_code"
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# version "2025-08", exclude_properties: %i[name] do
|
28
|
+
# field :name, type: :string, required: true
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# @see Verquest::Base::PublicClassMethods for available class methods
|
33
|
+
class Base
|
34
|
+
extend Base::HelperClassMethods
|
35
|
+
extend Base::PrivateClassMethods
|
36
|
+
extend Base::PublicClassMethods
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Verquest
|
2
|
+
# Configuration for the Verquest gem
|
3
|
+
#
|
4
|
+
# This class manages configuration settings for the Verquest gem, including
|
5
|
+
# validation behavior, JSON Schema version, and version resolution strategy.
|
6
|
+
# It's used to customize the behavior of versioned API requests.
|
7
|
+
#
|
8
|
+
# @example Basic configuration
|
9
|
+
# Verquest.configure do |config|
|
10
|
+
# config.validate_params = true
|
11
|
+
# config.current_version = -> { Current.api_version }
|
12
|
+
# end
|
13
|
+
class Configuration
|
14
|
+
# @!attribute [rw] validate_params
|
15
|
+
# Controls whether parameters are automatically validated against the schema
|
16
|
+
# @return [Boolean] true if validation is enabled, false otherwise
|
17
|
+
#
|
18
|
+
# @!attribute [rw] json_schema_version
|
19
|
+
# The JSON Schema draft version to use for validation and schema generation (see the json-schema gem)
|
20
|
+
# @return [Symbol] The JSON Schema version (e.g., :draft4, :draft5)
|
21
|
+
#
|
22
|
+
# @!attribute [rw] validation_error_handling
|
23
|
+
# Controls how errors during parameter processing are handled
|
24
|
+
# @return [Symbol] :raise to raise errors (default) or :result to return errors in the Result object
|
25
|
+
#
|
26
|
+
# @!attribute [rw] remove_extra_root_keys
|
27
|
+
# Controls if extra root keys not defined in the schema should be removed from the parameters
|
28
|
+
# @return [Boolean] true if extra keys should be removed, false otherwise
|
29
|
+
attr_accessor :validate_params, :json_schema_version, :validation_error_handling, :remove_extra_root_keys
|
30
|
+
|
31
|
+
# @!attribute [r] current_version
|
32
|
+
# A callable object that returns the current API version to use when not explicitly specified
|
33
|
+
# @return [#call] An object responding to call that determines the current version
|
34
|
+
#
|
35
|
+
# @!attribute [r] version_resolver
|
36
|
+
# The resolver used to map version strings/identifiers to version objects
|
37
|
+
# @return [#call] An object that responds to `call` for resolving versions
|
38
|
+
attr_reader :current_version, :version_resolver
|
39
|
+
|
40
|
+
# Initialize a new Configuration with default values
|
41
|
+
#
|
42
|
+
# @return [Configuration] A new configuration instance with default settings
|
43
|
+
def initialize
|
44
|
+
@validate_params = true
|
45
|
+
@json_schema_version = :draft6
|
46
|
+
@validation_error_handling = :raise # or :result
|
47
|
+
@remove_extra_root_keys = true
|
48
|
+
@version_resolver = VersionResolver
|
49
|
+
end
|
50
|
+
|
51
|
+
# Sets the current version strategy using a callable object
|
52
|
+
#
|
53
|
+
# @param current_version [#call] An object that returns the current version when called
|
54
|
+
# @raise [ArgumentError] If the provided value doesn't respond to call
|
55
|
+
# @return [#call] The callable object that was set
|
56
|
+
def current_version=(current_version)
|
57
|
+
raise ArgumentError, "The current_version must respond to a call method" unless current_version.respond_to?(:call)
|
58
|
+
|
59
|
+
@current_version = current_version
|
60
|
+
end
|
61
|
+
|
62
|
+
# Sets the version resolver
|
63
|
+
#
|
64
|
+
# @param version_resolver [#call] An object with a call method for resolving versions
|
65
|
+
# @raise [ArgumentError] If the provided resolver doesn't respond to call
|
66
|
+
# @return [#call] The resolver that was set
|
67
|
+
def version_resolver=(version_resolver)
|
68
|
+
raise ArgumentError, "The version_resolver must respond to a call method" unless version_resolver.respond_to?(:call)
|
69
|
+
|
70
|
+
@version_resolver = version_resolver
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Verquest
|
4
|
+
module Properties
|
5
|
+
# Array property type for schema generation and mapping
|
6
|
+
#
|
7
|
+
# Represents an array data structure in the schema with specified item type.
|
8
|
+
# Used to define arrays of scalar types (string, number, integer, boolean).
|
9
|
+
#
|
10
|
+
# @example Define an array of strings
|
11
|
+
# array = Verquest::Properties::Array.new(
|
12
|
+
# name: :tags,
|
13
|
+
# type: :string,
|
14
|
+
# required: true
|
15
|
+
# )
|
16
|
+
class Array < Base
|
17
|
+
# Initialize a new Array property
|
18
|
+
#
|
19
|
+
# @param name [Symbol] The name of the property
|
20
|
+
# @param type [Symbol] The type of items in the array
|
21
|
+
# @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 schema_options [Hash] Additional JSON schema options for this property
|
24
|
+
# @raise [ArgumentError] If attempting to map an array to the root
|
25
|
+
def initialize(name:, type:, map: nil, required: false, **schema_options)
|
26
|
+
raise ArgumentError, "You can not map array to the root" if map == "/"
|
27
|
+
|
28
|
+
@name = name
|
29
|
+
@type = type
|
30
|
+
@map = map
|
31
|
+
@required = required
|
32
|
+
@schema_options = schema_options
|
33
|
+
end
|
34
|
+
|
35
|
+
# Generate JSON schema definition for this array property
|
36
|
+
#
|
37
|
+
# @return [Hash] The schema definition for this array property
|
38
|
+
def to_schema
|
39
|
+
{
|
40
|
+
name => {
|
41
|
+
type: :array,
|
42
|
+
items: {type: type}
|
43
|
+
}.merge(schema_options)
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
# Create mapping for this array property
|
48
|
+
#
|
49
|
+
# @param key_prefix [Array<Symbol>] Prefix for the source key
|
50
|
+
# @param value_prefix [Array<Symbol>] Prefix for the target value
|
51
|
+
# @param mapping [Hash] The mapping hash to be updated
|
52
|
+
# @param version [String, nil] The version to create mapping for, defaults to configuration setting
|
53
|
+
# @return [Hash] The updated mapping hash
|
54
|
+
def mapping(key_prefix:, value_prefix:, mapping:, version: nil)
|
55
|
+
mapping[(key_prefix + [name]).join(".")] = mapping_value_key(value_prefix:)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
attr_reader :type, :schema_options
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Verquest
|
4
|
+
module Properties
|
5
|
+
# Base class for all property types
|
6
|
+
#
|
7
|
+
# This abstract class defines the interface for all property types
|
8
|
+
# in the Verquest schema system. All property classes should inherit
|
9
|
+
# from this base class and implement its required methods.
|
10
|
+
#
|
11
|
+
# @abstract Subclass and override {#to_schema}, {#mapping} to implement
|
12
|
+
class Base
|
13
|
+
# @!attribute [rw] name
|
14
|
+
# @return [Symbol] The name of the property
|
15
|
+
# @!attribute [rw] required
|
16
|
+
# @return [Boolean] Whether this property is required
|
17
|
+
# @!attribute [rw] map
|
18
|
+
# @return [String, nil] The mapping path for this property
|
19
|
+
attr_accessor :name, :required, :map
|
20
|
+
|
21
|
+
# Adds a child property to this property
|
22
|
+
# @abstract
|
23
|
+
# @param property [Verquest::Properties::Base] The property to add
|
24
|
+
# @raise [NoMethodError] This is an abstract method that must be overridden
|
25
|
+
def add(property)
|
26
|
+
raise NoMethodError
|
27
|
+
end
|
28
|
+
|
29
|
+
# Generates JSON schema for this property
|
30
|
+
# @abstract
|
31
|
+
# @return [Hash] The schema definition for this property
|
32
|
+
# @raise [NoMethodError] This is an abstract method that must be overridden
|
33
|
+
def to_schema
|
34
|
+
raise NoMethodError
|
35
|
+
end
|
36
|
+
|
37
|
+
# Generates validation schema for this property, defaults to the same as `to_schema`
|
38
|
+
# @param version [String, nil] The version to generate validation schema for
|
39
|
+
# @return [Hash] The validation schema for this property
|
40
|
+
def to_validation_schema(version: nil)
|
41
|
+
to_schema
|
42
|
+
end
|
43
|
+
|
44
|
+
# Creates mapping for this property
|
45
|
+
# @abstract
|
46
|
+
# @param key_prefix [Array<Symbol>] Prefix for the source key
|
47
|
+
# @param value_prefix [Array<String>] Prefix for the target value
|
48
|
+
# @param mapping [Hash] The mapping hash to be updated
|
49
|
+
# @param version [String, nil] The version to create mapping for
|
50
|
+
# @return [Hash] The updated mapping hash
|
51
|
+
# @raise [NoMethodError] This is an abstract method that must be overridden
|
52
|
+
def mapping(key_prefix:, value_prefix:, mapping:, version:)
|
53
|
+
raise NoMethodError
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
# Determines the mapping target key based on mapping configuration
|
59
|
+
# @param value_prefix [Array<String>] Prefix for the target value
|
60
|
+
# @param collection [Boolean] Whether this is a collection mapping
|
61
|
+
# @return [String] The target mapping key
|
62
|
+
def mapping_value_key(value_prefix:, collection: false)
|
63
|
+
value_key = if map.nil?
|
64
|
+
(value_prefix + [name]).join(".")
|
65
|
+
elsif map == "/"
|
66
|
+
""
|
67
|
+
elsif map.start_with?("/")
|
68
|
+
map.gsub(%r{^/}, "")
|
69
|
+
else
|
70
|
+
(value_prefix + map.split(".")).join(".")
|
71
|
+
end
|
72
|
+
|
73
|
+
if collection
|
74
|
+
value_key + "[]"
|
75
|
+
else
|
76
|
+
value_key
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Determines the mapping target value prefix based on mapping configuration
|
81
|
+
# @param value_prefix [Array<String>] Prefix for the target value
|
82
|
+
# @param collection [Boolean] Whether this is a collection mapping
|
83
|
+
# @return [Array<String>] The target mapping value prefix
|
84
|
+
def mapping_value_prefix(value_prefix:, collection: false)
|
85
|
+
value_prefix = if map.nil?
|
86
|
+
value_prefix + [name]
|
87
|
+
elsif map == "/"
|
88
|
+
[]
|
89
|
+
elsif map.start_with?("/")
|
90
|
+
map.gsub(%r{^/}, "").split(".")
|
91
|
+
else
|
92
|
+
value_prefix + map.split(".")
|
93
|
+
end
|
94
|
+
|
95
|
+
if collection && value_prefix.any?
|
96
|
+
last = value_prefix.pop
|
97
|
+
value_prefix.push((last.to_s + "[]").to_sym)
|
98
|
+
end
|
99
|
+
|
100
|
+
value_prefix
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|