verquest 0.6.4 → 0.7.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 +43 -1
- data/README.md +299 -45
- data/Rakefile +5 -0
- data/docs/rails.md +169 -0
- data/lib/verquest/base/condition_class_methods.rb +211 -0
- data/lib/verquest/base/private_class_methods.rb +31 -14
- data/lib/verquest/base/public_class_methods.rb +51 -1
- data/lib/verquest/base.rb +1 -0
- data/lib/verquest/conditions/branch.rb +111 -0
- data/lib/verquest/conditions/condition.rb +194 -0
- data/lib/verquest/conditions/conjunction.rb +39 -0
- data/lib/verquest/conditions/fragment_merge.rb +52 -0
- data/lib/verquest/conditions/property_merge.rb +47 -0
- data/lib/verquest/conditions/rule_set.rb +58 -0
- data/lib/verquest/conditions/rules/absent.rb +25 -0
- data/lib/verquest/conditions/rules/alternatives.rb +28 -0
- data/lib/verquest/conditions/rules/any.rb +27 -0
- data/lib/verquest/conditions/rules/equals.rb +22 -0
- data/lib/verquest/conditions/rules/names_rule.rb +23 -0
- data/lib/verquest/conditions/rules/none.rb +24 -0
- data/lib/verquest/conditions/rules/present.rb +22 -0
- data/lib/verquest/conditions/rules/property_rule.rb +48 -0
- data/lib/verquest/conditions/rules/satisfies.rb +22 -0
- data/lib/verquest/conditions/rules/value_in.rb +22 -0
- data/lib/verquest/conditions/rules.rb +17 -0
- data/lib/verquest/conditions.rb +36 -0
- data/lib/verquest/configuration.rb +19 -1
- data/lib/verquest/gem_version.rb +1 -1
- data/lib/verquest/helper_methods/conditional_properties.rb +126 -0
- data/lib/verquest/helper_methods/unnamed_one_of.rb +35 -0
- data/lib/verquest/properties/array.rb +15 -1
- data/lib/verquest/properties/base.rb +104 -10
- data/lib/verquest/properties/collection.rb +51 -18
- data/lib/verquest/properties/const.rb +2 -1
- data/lib/verquest/properties/object.rb +38 -4
- data/lib/verquest/properties/one_of/discriminated_schema.rb +84 -0
- data/lib/verquest/properties/one_of.rb +83 -44
- data/lib/verquest/properties/reference.rb +47 -16
- data/lib/verquest/query/coercer.rb +165 -0
- data/lib/verquest/query/definition_check.rb +91 -0
- data/lib/verquest/query/parameters.rb +79 -0
- data/lib/verquest/query.rb +25 -0
- data/lib/verquest/transformer/collection.rb +32 -0
- data/lib/verquest/transformer/const_as_enum.rb +47 -0
- data/lib/verquest/transformer/layout.rb +83 -0
- data/lib/verquest/transformer/mapping_layout.rb +166 -0
- data/lib/verquest/transformer/one_of.rb +111 -0
- data/lib/verquest/transformer/path.rb +188 -0
- data/lib/verquest/transformer/plan.rb +183 -0
- data/lib/verquest/transformer/scope.rb +64 -0
- data/lib/verquest/transformer.rb +43 -880
- data/lib/verquest/version.rb +152 -79
- data/lib/verquest.rb +6 -0
- metadata +42 -7
|
@@ -18,6 +18,9 @@ module Verquest
|
|
|
18
18
|
# item: ProductRequest
|
|
19
19
|
# )
|
|
20
20
|
class Collection < Base
|
|
21
|
+
include HelperMethods::ConditionalProperties
|
|
22
|
+
include HelperMethods::UnnamedOneOf
|
|
23
|
+
|
|
21
24
|
# Initialize a new Collection property
|
|
22
25
|
#
|
|
23
26
|
# @param name [String, Symbol] The name of the property
|
|
@@ -31,6 +34,7 @@ module Verquest
|
|
|
31
34
|
raise ArgumentError, "You can not map collection to the root" if map == "/"
|
|
32
35
|
|
|
33
36
|
@properties = {}
|
|
37
|
+
@conditions = []
|
|
34
38
|
|
|
35
39
|
@name = name.to_s
|
|
36
40
|
@item = item
|
|
@@ -51,9 +55,21 @@ module Verquest
|
|
|
51
55
|
# @param property [Verquest::Properties::Base] The property to add to the collection items
|
|
52
56
|
# @return [Verquest::Properties::Base] The added property
|
|
53
57
|
def add(property)
|
|
58
|
+
check_unnamed_one_of(property)
|
|
54
59
|
properties[property.name] = property
|
|
55
60
|
end
|
|
56
61
|
|
|
62
|
+
# Add a condition to this collection's item definition
|
|
63
|
+
#
|
|
64
|
+
# @param condition [Verquest::Conditions::Condition] The condition to add
|
|
65
|
+
# @return [Array<Verquest::Conditions::Condition>] The conditions
|
|
66
|
+
# @raise [ArgumentError] If the collection items are defined by `item:`
|
|
67
|
+
def add_condition(condition)
|
|
68
|
+
raise ArgumentError, "condition can not be declared in a collection defined by item:" if has_item?
|
|
69
|
+
|
|
70
|
+
super
|
|
71
|
+
end
|
|
72
|
+
|
|
57
73
|
# Check if this collection references an external item schema
|
|
58
74
|
#
|
|
59
75
|
# @return [Boolean] True if the collection uses an external reference
|
|
@@ -61,18 +77,21 @@ module Verquest
|
|
|
61
77
|
!item.nil?
|
|
62
78
|
end
|
|
63
79
|
|
|
64
|
-
# Check if
|
|
80
|
+
# Check if the items are a oneOf: an unnamed oneOf is the collection's only property
|
|
65
81
|
#
|
|
66
|
-
# @return [Boolean] True if the
|
|
82
|
+
# @return [Boolean] True if the item schema is the oneOf itself
|
|
67
83
|
def has_one_of?
|
|
68
|
-
|
|
84
|
+
# A named oneOf is a property of the item, like any other
|
|
85
|
+
properties.values.size == 1 && properties.values.first.is_a?(Verquest::Properties::OneOf) && properties.values.first.name.nil?
|
|
69
86
|
end
|
|
70
87
|
|
|
71
88
|
# Returns the oneOf property if this collection contains one
|
|
72
89
|
#
|
|
73
90
|
# @return [Verquest::Properties::OneOf, nil] The oneOf property or nil
|
|
91
|
+
# @raise [ArgumentError] If the collection also has conditions
|
|
74
92
|
def one_of_property
|
|
75
93
|
return nil unless has_one_of?
|
|
94
|
+
raise ArgumentError, "Conditions can not be declared next to a one_of in a collection" if conditions.any?
|
|
76
95
|
|
|
77
96
|
properties.values.first
|
|
78
97
|
end
|
|
@@ -104,10 +123,11 @@ module Verquest
|
|
|
104
123
|
"items" => {
|
|
105
124
|
"type" => "object",
|
|
106
125
|
"required" => required_properties,
|
|
107
|
-
"properties" =>
|
|
126
|
+
"properties" => all_properties.transform_values { |property| property.to_schema[property.name] },
|
|
108
127
|
"additionalProperties" => Verquest.configuration.default_additional_properties
|
|
109
128
|
}.tap do |schema|
|
|
110
129
|
schema["dependentRequired"] = dependent_required_properties if dependent_required_properties.any?
|
|
130
|
+
add_conditions(schema)
|
|
111
131
|
end
|
|
112
132
|
}.merge(schema_options)
|
|
113
133
|
}
|
|
@@ -141,10 +161,11 @@ module Verquest
|
|
|
141
161
|
"items" => {
|
|
142
162
|
"type" => "object",
|
|
143
163
|
"required" => required_properties,
|
|
144
|
-
"properties" =>
|
|
164
|
+
"properties" => all_properties.transform_values { |property| property.to_validation_schema(version: version)[property.name] },
|
|
145
165
|
"additionalProperties" => Verquest.configuration.default_additional_properties
|
|
146
166
|
}.tap do |schema|
|
|
147
167
|
schema["dependentRequired"] = dependent_required_properties if dependent_required_properties.any?
|
|
168
|
+
add_conditions(schema)
|
|
148
169
|
end
|
|
149
170
|
}.merge(schema_options)
|
|
150
171
|
}
|
|
@@ -174,24 +195,36 @@ module Verquest
|
|
|
174
195
|
# @return [void]
|
|
175
196
|
def mapping(key_prefix:, value_prefix:, mapping:, version:)
|
|
176
197
|
if has_item?
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
reference_mapping = item.mapping(version:).dup
|
|
180
|
-
reference_mapping.transform_keys! { |k| "#{(key_prefix + [name]).join("/")}[]/#{k}" }
|
|
181
|
-
reference_mapping.transform_values! { |v| "#{value_key_prefix}/#{v}" }
|
|
182
|
-
|
|
183
|
-
mapping.merge!(reference_mapping)
|
|
184
|
-
elsif has_one_of?
|
|
185
|
-
one_of_mapping = {}
|
|
186
|
-
one_of_property.mapping(key_prefix: key_prefix + ["#{name}[]"], value_prefix: mapping_value_prefix(value_prefix: value_prefix, collection: true), mapping: one_of_mapping, version:)
|
|
187
|
-
mapping.merge!(one_of_mapping)
|
|
198
|
+
add_request_mapping(item, key_prefix: key_prefix + ["#{name}[]"], value_prefix: mapping_value_prefix(value_prefix: value_prefix, collection: true), mapping:, version:)
|
|
188
199
|
else
|
|
189
|
-
|
|
190
|
-
|
|
200
|
+
children = has_one_of? ? [one_of_property] : all_properties.values
|
|
201
|
+
children.each do |property|
|
|
202
|
+
add_child_mapping(property, key_prefix: key_prefix + ["#{name}[]"], value_prefix: mapping_value_prefix(value_prefix: value_prefix, collection: true), mapping:, version:)
|
|
191
203
|
end
|
|
192
204
|
end
|
|
193
205
|
end
|
|
194
206
|
|
|
207
|
+
# Adds this collection and its item properties to a transformer layout
|
|
208
|
+
#
|
|
209
|
+
# The collection's source and target paths are added too, so the plan
|
|
210
|
+
# knows where the items go when they hold a oneOf.
|
|
211
|
+
#
|
|
212
|
+
# @param key_prefix [Array<String>] Prefix for the source key
|
|
213
|
+
# @param value_prefix [Array<String>] Prefix for the target value
|
|
214
|
+
# @param layout [Verquest::Transformer::Layout] The layout to add to
|
|
215
|
+
# @param version [String, nil] The version to create the layout for
|
|
216
|
+
# @return [void]
|
|
217
|
+
def layout(key_prefix:, value_prefix:, layout:, version:)
|
|
218
|
+
item_key_prefix = key_prefix + ["#{name}[]"]
|
|
219
|
+
item_value_prefix = mapping_value_prefix(value_prefix:, collection: true)
|
|
220
|
+
layout.add_collection(item_key_prefix.join("/"), item_value_prefix.join("/"))
|
|
221
|
+
return add_request_layout(item, key_prefix: item_key_prefix, value_prefix: item_value_prefix, layout:, version:) if has_item?
|
|
222
|
+
|
|
223
|
+
(has_one_of? ? [one_of_property] : all_properties.values).each do |property|
|
|
224
|
+
property.layout(key_prefix: item_key_prefix, value_prefix: item_value_prefix, layout:, version:)
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
195
228
|
private
|
|
196
229
|
|
|
197
230
|
attr_reader :item, :schema_options, :properties, :type
|
|
@@ -30,7 +30,8 @@ module Verquest
|
|
|
30
30
|
# @return [Hash] The schema definition for this constant
|
|
31
31
|
def to_schema
|
|
32
32
|
{
|
|
33
|
-
|
|
33
|
+
# Draft 4 and OpenAPI 3.0 have no const, but a one-value enum means the same
|
|
34
|
+
name => nullable_schema((Verquest.configuration.const_keyword? ? {"const" => value} : {"enum" => [value]}).merge(schema_options))
|
|
34
35
|
}
|
|
35
36
|
end
|
|
36
37
|
|
|
@@ -12,6 +12,9 @@ module Verquest
|
|
|
12
12
|
# address.add(Verquest::Properties::Field.new(name: :street, type: :string))
|
|
13
13
|
# address.add(Verquest::Properties::Field.new(name: :city, type: :string, required: true))
|
|
14
14
|
class Object < Base
|
|
15
|
+
include HelperMethods::ConditionalProperties
|
|
16
|
+
include HelperMethods::UnnamedOneOf
|
|
17
|
+
|
|
15
18
|
# Initialize a new Object property
|
|
16
19
|
#
|
|
17
20
|
# @param name [String, Symbol] The name of the property
|
|
@@ -21,6 +24,7 @@ module Verquest
|
|
|
21
24
|
# @param schema_options [Hash] Additional JSON schema options for this property
|
|
22
25
|
def initialize(name:, required: false, nullable: false, map: nil, **schema_options)
|
|
23
26
|
@properties = {}
|
|
27
|
+
@conditions = []
|
|
24
28
|
|
|
25
29
|
@name = name.to_s
|
|
26
30
|
@required = required
|
|
@@ -44,6 +48,7 @@ module Verquest
|
|
|
44
48
|
# @param property [Verquest::Properties::Base] The property to add to this object
|
|
45
49
|
# @return [Verquest::Properties::Base] The added property
|
|
46
50
|
def add(property)
|
|
51
|
+
check_unnamed_one_of(property)
|
|
47
52
|
properties[property.name.to_s] = property
|
|
48
53
|
end
|
|
49
54
|
|
|
@@ -55,9 +60,10 @@ module Verquest
|
|
|
55
60
|
name => {
|
|
56
61
|
"type" => type,
|
|
57
62
|
"required" => required_properties,
|
|
58
|
-
"properties" =>
|
|
63
|
+
"properties" => all_properties.transform_values { |property| property.to_schema[property.name] }
|
|
59
64
|
}.merge(schema_options).tap do |schema|
|
|
60
65
|
schema["dependentRequired"] = dependent_required_properties if dependent_required_properties.any?
|
|
66
|
+
add_conditions(schema)
|
|
61
67
|
end
|
|
62
68
|
}
|
|
63
69
|
end
|
|
@@ -71,23 +77,51 @@ module Verquest
|
|
|
71
77
|
name => {
|
|
72
78
|
"type" => type,
|
|
73
79
|
"required" => required_properties,
|
|
74
|
-
"properties" =>
|
|
80
|
+
"properties" => all_properties.transform_values { |property| property.to_validation_schema(version: version)[property.name] }
|
|
75
81
|
}.merge(schema_options).tap do |schema|
|
|
76
82
|
schema["dependentRequired"] = dependent_required_properties if dependent_required_properties.any?
|
|
83
|
+
add_conditions(schema)
|
|
77
84
|
end
|
|
78
85
|
}
|
|
79
86
|
end
|
|
80
87
|
|
|
81
88
|
# Create mapping for this object property and all its children
|
|
82
89
|
#
|
|
90
|
+
# Each nested oneOf gets its own mapping in the `_oneOfs` array, so its
|
|
91
|
+
# metadata can't clash with another oneOf.
|
|
92
|
+
#
|
|
83
93
|
# @param key_prefix [Array<String>] Prefix for the source key
|
|
84
94
|
# @param value_prefix [Array<String>] Prefix for the target value
|
|
85
95
|
# @param mapping [Hash] The mapping hash to be updated
|
|
86
96
|
# @param version [String, nil] The version to create mapping for
|
|
87
97
|
# @return [void]
|
|
88
98
|
def mapping(key_prefix:, value_prefix:, mapping:, version: nil)
|
|
89
|
-
|
|
90
|
-
property
|
|
99
|
+
all_properties.values.each do |property|
|
|
100
|
+
add_child_mapping(property, key_prefix: key_prefix + [name], value_prefix: mapping_value_prefix(value_prefix:), mapping:, version:)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Adds this object's properties to a transformer layout
|
|
105
|
+
#
|
|
106
|
+
# @param key_prefix [Array<String>] Prefix for the source key
|
|
107
|
+
# @param value_prefix [Array<String>] Prefix for the target value
|
|
108
|
+
# @param layout [Verquest::Transformer::Layout] The layout to add to
|
|
109
|
+
# @param version [String, nil] The version to create the layout for
|
|
110
|
+
# @return [void]
|
|
111
|
+
def layout(key_prefix:, value_prefix:, layout:, version:)
|
|
112
|
+
all_properties.each_value do |property|
|
|
113
|
+
property.layout(key_prefix: key_prefix + [name], value_prefix: mapping_value_prefix(value_prefix:), layout:, version:)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Returns the query parameter options of this object and its properties
|
|
118
|
+
#
|
|
119
|
+
# @param prefix [Array<String>] Path of the parent property
|
|
120
|
+
# @param version [String] The version to resolve references with
|
|
121
|
+
# @return [Hash{Array<String> => Hash}] The options of each property path
|
|
122
|
+
def query_options(prefix:, version:)
|
|
123
|
+
all_properties.each_value.with_object(super) do |property, options|
|
|
124
|
+
options.merge!(property.query_options(prefix: prefix + [name], version:))
|
|
91
125
|
end
|
|
92
126
|
end
|
|
93
127
|
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Verquest
|
|
4
|
+
module Properties
|
|
5
|
+
class OneOf < Base
|
|
6
|
+
# Builds a self-contained oneOf validation schema that keeps the discriminator
|
|
7
|
+
#
|
|
8
|
+
# OpenAPI validators (json_schemer's OpenAPI 3.0 and 3.1 dialects) select the
|
|
9
|
+
# discriminated variant through each oneOf entry's `$ref`, so the variants move
|
|
10
|
+
# into local definitions. The content-based identifier makes those `$ref`s resolve
|
|
11
|
+
# against this schema wherever it is embedded, and identical content always gets
|
|
12
|
+
# the same identifier. Other dialects ignore the discriminator and validate every variant.
|
|
13
|
+
#
|
|
14
|
+
# The identifier is set as both `$id` (draft 6 and later, OpenAPI 3.1) and `id`
|
|
15
|
+
# (draft 4, OpenAPI 3.0), and the variants live under `definitions`, which every
|
|
16
|
+
# dialect can reference. The output is the same whatever `json_schema_version` is.
|
|
17
|
+
#
|
|
18
|
+
# @example
|
|
19
|
+
# DiscriminatedSchema.new(definitions: {"dog" => dog_schema, "cat" => cat_schema}, discriminator: "type").to_h
|
|
20
|
+
# # => {
|
|
21
|
+
# # "$id" => "https://verquest.local/one_of/3f1c9a0b2d4e5f67",
|
|
22
|
+
# # "id" => "https://verquest.local/one_of/3f1c9a0b2d4e5f67",
|
|
23
|
+
# # "definitions" => {"dog" => dog_schema, "cat" => cat_schema},
|
|
24
|
+
# # "oneOf" => [{"$ref" => "#/definitions/dog"}, {"$ref" => "#/definitions/cat"}],
|
|
25
|
+
# # "discriminator" => {"propertyName" => "type", "mapping" => {"dog" => "#/definitions/dog", "cat" => "#/definitions/cat"}}
|
|
26
|
+
# # }
|
|
27
|
+
class DiscriminatedSchema
|
|
28
|
+
# Base URI for the schema identifier
|
|
29
|
+
ID_PREFIX = "https://verquest.local/one_of/"
|
|
30
|
+
|
|
31
|
+
# Initialize a new DiscriminatedSchema
|
|
32
|
+
#
|
|
33
|
+
# @param definitions [Hash<String, Hash>] Variant name (discriminator value) => inline validation schema
|
|
34
|
+
# @param discriminator [String] The discriminator property name
|
|
35
|
+
def initialize(definitions:, discriminator:)
|
|
36
|
+
@definitions = definitions
|
|
37
|
+
@discriminator = discriminator
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Builds the validation schema
|
|
41
|
+
#
|
|
42
|
+
# @return [Hash] Schema with identifiers, definitions, oneOf refs, and discriminator
|
|
43
|
+
def to_h
|
|
44
|
+
{"$id" => id, "id" => id}.merge(body)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
# @!attribute [r] definitions
|
|
50
|
+
# @return [Hash<String, Hash>] Variant name => inline validation schema
|
|
51
|
+
# @!attribute [r] discriminator
|
|
52
|
+
# @return [String] The discriminator property name
|
|
53
|
+
attr_reader :definitions, :discriminator
|
|
54
|
+
|
|
55
|
+
# The schema without the identifiers
|
|
56
|
+
#
|
|
57
|
+
# @return [Hash] Definitions, oneOf refs, and discriminator
|
|
58
|
+
def body
|
|
59
|
+
@body ||= {
|
|
60
|
+
"definitions" => definitions,
|
|
61
|
+
"oneOf" => refs.values.map { |ref| {"$ref" => ref} },
|
|
62
|
+
"discriminator" => {"propertyName" => discriminator, "mapping" => refs}
|
|
63
|
+
}
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Content-based identifier, so identical schemas share it
|
|
67
|
+
#
|
|
68
|
+
# `allow_nan` keeps schemas with infinite or NaN numbers (e.g. `maximum: Float::INFINITY`) hashable.
|
|
69
|
+
#
|
|
70
|
+
# @return [String] The identifier URI
|
|
71
|
+
def id
|
|
72
|
+
@id ||= "#{ID_PREFIX}#{Digest::SHA256.hexdigest(JSON.generate(body, allow_nan: true))[0, 16]}"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Local `$ref` for each variant, keyed by variant name
|
|
76
|
+
#
|
|
77
|
+
# @return [Hash<String, String>] Variant name => `$ref` string
|
|
78
|
+
def refs
|
|
79
|
+
@refs ||= definitions.keys.to_h { |name| [name, "#/definitions/#{name}"] }
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -30,6 +30,9 @@ module Verquest
|
|
|
30
30
|
# @example oneOf without discriminator (pure JSON Schema validation)
|
|
31
31
|
# one_of = Verquest::Properties::OneOf.new(name: :value)
|
|
32
32
|
# # Validates that exactly one schema matches
|
|
33
|
+
#
|
|
34
|
+
# With a discriminator, the validation schema keeps the discriminator (see DiscriminatedSchema),
|
|
35
|
+
# so OpenAPI 3.0 and 3.1 validators validate only the variant the discriminator selects.
|
|
33
36
|
class OneOf < Base
|
|
34
37
|
# @return [String, nil] The discriminator property name for schema selection
|
|
35
38
|
attr_reader :discriminator
|
|
@@ -113,8 +116,33 @@ module Verquest
|
|
|
113
116
|
|
|
114
117
|
build_variant_mappings(mapping, source_prefix, target_prefix, version)
|
|
115
118
|
store_discriminator_path(mapping, source_prefix)
|
|
116
|
-
store_variant_schemas(mapping, version) unless discriminator
|
|
117
|
-
store_nullable_metadata(mapping, target_prefix:) if nullable
|
|
119
|
+
store_variant_schemas(mapping, source_prefix, version) unless discriminator
|
|
120
|
+
store_nullable_metadata(mapping, source_prefix:, target_prefix:) if nullable
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Adds this oneOf to a transformer layout
|
|
124
|
+
#
|
|
125
|
+
# Each variant gets its own layout, so it can hold oneOfs and collections.
|
|
126
|
+
# Like in the mapping, variant properties sit directly under the oneOf
|
|
127
|
+
# path, not under the variant name.
|
|
128
|
+
#
|
|
129
|
+
# @param key_prefix [Array<String>] Prefix for the source key
|
|
130
|
+
# @param value_prefix [Array<String>] Prefix for the target value
|
|
131
|
+
# @param layout [Verquest::Transformer::Layout] The layout to add to
|
|
132
|
+
# @param version [String, nil] The version to create the layout for
|
|
133
|
+
# @return [void]
|
|
134
|
+
def layout(key_prefix:, value_prefix:, layout:, version:)
|
|
135
|
+
freeze_schemas
|
|
136
|
+
source_prefix = compute_source_prefix(key_prefix)
|
|
137
|
+
target_prefix = compute_target_prefix(value_prefix)
|
|
138
|
+
|
|
139
|
+
layout.add_one_of(Transformer::Layout::OneOfLayout.new(
|
|
140
|
+
path: source_prefix.join("/"),
|
|
141
|
+
discriminator: discriminator && join_path(source_prefix, discriminator),
|
|
142
|
+
variants: schemas.each_value.to_h { |schema| [schema.name, variant_layout(schema, source_prefix, target_prefix, layout, version)] },
|
|
143
|
+
variant_schemas: (variant_schemas(version:) unless discriminator),
|
|
144
|
+
null_target: (target_prefix.join("/") if nullable && !source_prefix.empty?)
|
|
145
|
+
))
|
|
118
146
|
end
|
|
119
147
|
|
|
120
148
|
# Returns validation schemas for all variants
|
|
@@ -191,7 +219,7 @@ module Verquest
|
|
|
191
219
|
# Builds variant mappings for each schema option
|
|
192
220
|
#
|
|
193
221
|
# Handles both Reference and Object schemas:
|
|
194
|
-
# - Reference:
|
|
222
|
+
# - Reference: maps the referenced request's properties under the oneOf path
|
|
195
223
|
# - Object: builds mapping from child properties directly
|
|
196
224
|
#
|
|
197
225
|
# @param mapping [Hash] The mapping hash to populate
|
|
@@ -211,6 +239,9 @@ module Verquest
|
|
|
211
239
|
|
|
212
240
|
# Builds mapping for a Reference variant
|
|
213
241
|
#
|
|
242
|
+
# Maps the referenced request's properties under the oneOf path, so its
|
|
243
|
+
# oneOfs land in the variant's `_oneOfs` with full paths.
|
|
244
|
+
#
|
|
214
245
|
# @param mapping [Hash] The mapping hash to populate
|
|
215
246
|
# @param schema [Verquest::Properties::Reference] The reference schema
|
|
216
247
|
# @param source_prefix [Array<String>] Source path prefix
|
|
@@ -218,15 +249,10 @@ module Verquest
|
|
|
218
249
|
# @param version [String, nil] The version for schema resolution
|
|
219
250
|
# @return [void]
|
|
220
251
|
def build_reference_variant_mapping(mapping, schema, source_prefix, target_prefix, version)
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
variant_target_prefix = compute_reference_target_prefix(reference_map, target_prefix)
|
|
252
|
+
variant_target_prefix = compute_reference_target_prefix(schema.send(:map), target_prefix)
|
|
253
|
+
variant_mapping = mapping[schema.name] = {}
|
|
224
254
|
|
|
225
|
-
|
|
226
|
-
reference_mapping,
|
|
227
|
-
source_prefix,
|
|
228
|
-
variant_target_prefix
|
|
229
|
-
)
|
|
255
|
+
add_request_mapping(schema.send(:from), key_prefix: source_prefix, value_prefix: variant_target_prefix, mapping: variant_mapping, version:)
|
|
230
256
|
end
|
|
231
257
|
|
|
232
258
|
# Builds mapping for an inline Object variant
|
|
@@ -242,34 +268,34 @@ module Verquest
|
|
|
242
268
|
# @param version [String, nil] The version for schema resolution
|
|
243
269
|
# @return [void]
|
|
244
270
|
def build_object_variant_mapping(mapping, schema, source_prefix, target_prefix, version)
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
variant_target_prefix = compute_reference_target_prefix(object_map, target_prefix)
|
|
271
|
+
variant_target_prefix = compute_reference_target_prefix(schema.send(:map), target_prefix)
|
|
272
|
+
variant_mapping = mapping[schema.name] = {}
|
|
248
273
|
|
|
249
|
-
# Build mapping from object's child properties
|
|
250
274
|
# Properties are mapped directly under source_prefix (not source_prefix + object_name)
|
|
251
|
-
# to match how Reference variants work
|
|
252
|
-
schema.
|
|
253
|
-
property
|
|
254
|
-
key_prefix: source_prefix,
|
|
255
|
-
value_prefix: variant_target_prefix,
|
|
256
|
-
mapping: object_mapping,
|
|
257
|
-
version: version
|
|
258
|
-
)
|
|
275
|
+
# to match how Reference variants work; a nested oneOf lands in the variant's _oneOfs
|
|
276
|
+
schema.all_properties.each_value do |property|
|
|
277
|
+
add_child_mapping(property, key_prefix: source_prefix, value_prefix: variant_target_prefix, mapping: variant_mapping, version:)
|
|
259
278
|
end
|
|
260
|
-
|
|
261
|
-
mapping[schema.name] = object_mapping
|
|
262
279
|
end
|
|
263
280
|
|
|
264
|
-
#
|
|
281
|
+
# Adds a variant's properties to its layout, directly under the oneOf path
|
|
265
282
|
#
|
|
266
|
-
# @param
|
|
267
|
-
# @param source_prefix [Array<String>]
|
|
268
|
-
# @param target_prefix [Array<String>]
|
|
269
|
-
# @
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
283
|
+
# @param schema [Verquest::Properties::Reference, Verquest::Properties::Object] The variant
|
|
284
|
+
# @param source_prefix [Array<String>] The oneOf source path
|
|
285
|
+
# @param target_prefix [Array<String>] The oneOf target path
|
|
286
|
+
# @param layout [Verquest::Transformer::Layout] The layout holding the oneOf
|
|
287
|
+
# @param version [String, nil] The version for schema resolution
|
|
288
|
+
# @return [Verquest::Transformer::Layout] The variant layout
|
|
289
|
+
def variant_layout(schema, source_prefix, target_prefix, layout, version)
|
|
290
|
+
variant_target_prefix = compute_reference_target_prefix(schema.send(:map), target_prefix)
|
|
291
|
+
layout.variant.tap do |variant|
|
|
292
|
+
if schema.is_a?(Verquest::Properties::Reference)
|
|
293
|
+
add_request_layout(schema.send(:from), key_prefix: source_prefix, value_prefix: variant_target_prefix, layout: variant, version:)
|
|
294
|
+
else
|
|
295
|
+
schema.all_properties.each_value do |property|
|
|
296
|
+
property.layout(key_prefix: source_prefix, value_prefix: variant_target_prefix, layout: variant, version:)
|
|
297
|
+
end
|
|
298
|
+
end
|
|
273
299
|
end
|
|
274
300
|
end
|
|
275
301
|
|
|
@@ -295,11 +321,13 @@ module Verquest
|
|
|
295
321
|
# schemas to determine which variant matches the input data.
|
|
296
322
|
#
|
|
297
323
|
# @param mapping [Hash] The mapping hash to update
|
|
324
|
+
# @param source_prefix [Array<String>] The source path prefix, ending with the oneOf name
|
|
298
325
|
# @param version [String, nil] The version for schema resolution
|
|
299
326
|
# @return [void]
|
|
300
|
-
def store_variant_schemas(mapping, version)
|
|
327
|
+
def store_variant_schemas(mapping, source_prefix, version)
|
|
301
328
|
mapping["_variant_schemas"] = variant_schemas(version: version)
|
|
302
|
-
|
|
329
|
+
# An unnamed oneOf is only at the root when nothing references it
|
|
330
|
+
mapping["_variant_path"] = source_prefix.join("/") unless source_prefix.empty?
|
|
303
331
|
end
|
|
304
332
|
|
|
305
333
|
# Stores nullable metadata in the mapping
|
|
@@ -308,13 +336,14 @@ module Verquest
|
|
|
308
336
|
# without attempting variant resolution.
|
|
309
337
|
#
|
|
310
338
|
# @param mapping [Hash] The mapping hash to update
|
|
339
|
+
# @param source_prefix [Array<String>] The source path prefix, ending with the oneOf name
|
|
311
340
|
# @param target_prefix [Array<String>] The target path prefix for the oneOf property
|
|
312
341
|
# @return [void]
|
|
313
|
-
def store_nullable_metadata(mapping, target_prefix:)
|
|
342
|
+
def store_nullable_metadata(mapping, source_prefix:, target_prefix:)
|
|
314
343
|
mapping["_nullable"] = true
|
|
315
|
-
return if
|
|
344
|
+
return if source_prefix.empty?
|
|
316
345
|
|
|
317
|
-
mapping["_nullable_path"] =
|
|
346
|
+
mapping["_nullable_path"] = source_prefix.join("/")
|
|
318
347
|
mapping["_nullable_target_path"] = target_prefix.join("/")
|
|
319
348
|
end
|
|
320
349
|
|
|
@@ -369,14 +398,16 @@ module Verquest
|
|
|
369
398
|
|
|
370
399
|
# Builds the validation schema structure with inline definitions
|
|
371
400
|
#
|
|
372
|
-
#
|
|
373
|
-
#
|
|
374
|
-
#
|
|
401
|
+
# Without a discriminator, the variants are inlined in the oneOf array.
|
|
402
|
+
# With a discriminator, DiscriminatedSchema builds a self-contained schema
|
|
403
|
+
# that keeps the discriminator.
|
|
375
404
|
#
|
|
376
405
|
# @param version [String, nil] The version to generate validation schema for
|
|
377
|
-
# @return [Hash] Validation schema with oneOf array
|
|
406
|
+
# @return [Hash] Validation schema with oneOf array
|
|
378
407
|
def build_validation_schema(version:)
|
|
379
|
-
{schema_keyword => collect_inline_schemas(version)}
|
|
408
|
+
return {schema_keyword => collect_inline_schemas(version)} unless discriminator
|
|
409
|
+
|
|
410
|
+
DiscriminatedSchema.new(definitions: collect_inline_definitions(version), discriminator: discriminator).to_h
|
|
380
411
|
end
|
|
381
412
|
|
|
382
413
|
# Collects $ref schema references for all variants
|
|
@@ -391,7 +422,15 @@ module Verquest
|
|
|
391
422
|
# @param version [String, nil] The version for schema resolution
|
|
392
423
|
# @return [Array<Hash>] Array of inline schema definitions
|
|
393
424
|
def collect_inline_schemas(version)
|
|
394
|
-
|
|
425
|
+
collect_inline_definitions(version).values
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
# Collects inline schema definitions keyed by variant name
|
|
429
|
+
#
|
|
430
|
+
# @param version [String, nil] The version for schema resolution
|
|
431
|
+
# @return [Hash<String, Hash>] Variant name => inline schema definition
|
|
432
|
+
def collect_inline_definitions(version)
|
|
433
|
+
schemas.transform_values { |schema| schema.to_validation_schema(version: version)[schema.name] }
|
|
395
434
|
end
|
|
396
435
|
|
|
397
436
|
# Adds discriminator information to the schema if present
|
|
@@ -56,37 +56,68 @@ module Verquest
|
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
# Create mapping for this reference property
|
|
59
|
-
#
|
|
59
|
+
#
|
|
60
|
+
# The referenced request's properties map themselves under this
|
|
61
|
+
# reference's prefixes. A reference to a single property maps that
|
|
62
|
+
# property as if it were declared here, with this reference's name and map:.
|
|
60
63
|
#
|
|
61
64
|
# @param key_prefix [Array<String>] Prefix for the source key
|
|
62
65
|
# @param value_prefix [Array<String>] Prefix for the target value
|
|
63
66
|
# @param mapping [Hash] The mapping hash to be updated
|
|
64
67
|
# @param version [String] The version to create mapping for
|
|
65
68
|
# @return [void]
|
|
69
|
+
# @raise [Verquest::PropertyNotFoundError] If the referenced property doesn't exist
|
|
66
70
|
def mapping(key_prefix:, value_prefix:, mapping:, version:)
|
|
67
|
-
|
|
68
|
-
value_key_prefix = mapping_value_key(value_prefix:)
|
|
71
|
+
return add_request_mapping(from, key_prefix: key_prefix + [name], value_prefix: mapping_value_prefix(value_prefix:), mapping:, version:) unless property
|
|
69
72
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
(key_prefix + [name]).join("/") => value_key_prefix
|
|
74
|
-
}
|
|
75
|
-
else
|
|
76
|
-
if value_key_prefix != "" && !value_key_prefix.end_with?("/")
|
|
77
|
-
value_key_prefix = "#{value_key_prefix}/"
|
|
78
|
-
end
|
|
73
|
+
referenced = from.resolve(version)
|
|
74
|
+
add_child_mapping(referenced.fetch_property(property).renamed(name:, map:), key_prefix:, value_prefix:, mapping:, version: referenced.name)
|
|
75
|
+
end
|
|
79
76
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
77
|
+
# Adds the referenced request's properties, or the referenced property, to a transformer layout
|
|
78
|
+
#
|
|
79
|
+
# @param key_prefix [Array<String>] Prefix for the source key
|
|
80
|
+
# @param value_prefix [Array<String>] Prefix for the target value
|
|
81
|
+
# @param layout [Verquest::Transformer::Layout] The layout to add to
|
|
82
|
+
# @param version [String, nil] The version to create the layout for
|
|
83
|
+
# @return [void]
|
|
84
|
+
# @raise [Verquest::PropertyNotFoundError] If the referenced property doesn't exist
|
|
85
|
+
def layout(key_prefix:, value_prefix:, layout:, version:)
|
|
86
|
+
return add_request_layout(from, key_prefix: key_prefix + [name], value_prefix: mapping_value_prefix(value_prefix:), layout:, version:) unless property
|
|
87
|
+
|
|
88
|
+
referenced = from.resolve(version)
|
|
89
|
+
referenced.fetch_property(property).renamed(name:, map:).layout(key_prefix:, value_prefix:, layout:, version: referenced.name)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Returns the query parameter options of the referenced request, or of
|
|
93
|
+
# the referenced property renamed to this reference
|
|
94
|
+
#
|
|
95
|
+
# This reference's own parameter examples replace the referenced ones.
|
|
96
|
+
#
|
|
97
|
+
# @param prefix [Array<String>] Path of the parent property
|
|
98
|
+
# @param version [String] The version to resolve references with
|
|
99
|
+
# @return [Hash{Array<String> => Hash}] The options of each property path
|
|
100
|
+
# @raise [Verquest::PropertyNotFoundError] If the referenced property doesn't exist
|
|
101
|
+
def query_options(prefix:, version:)
|
|
102
|
+
referenced = from.resolve(version)
|
|
103
|
+
return referenced_property_options(referenced, prefix:) if property
|
|
83
104
|
|
|
84
|
-
|
|
105
|
+
referenced.all_properties.each_value.with_object(super) do |child, options|
|
|
106
|
+
options.merge!(child.query_options(prefix: prefix + [name], version: referenced.name))
|
|
107
|
+
end
|
|
85
108
|
end
|
|
86
109
|
|
|
87
110
|
private
|
|
88
111
|
|
|
89
112
|
attr_reader :from, :property
|
|
113
|
+
|
|
114
|
+
# @return [Hash{Array<String> => Hash}] The options of the referenced property under this reference's name
|
|
115
|
+
def referenced_property_options(referenced, prefix:)
|
|
116
|
+
copy = referenced.fetch_property(property).renamed(name:, map:)
|
|
117
|
+
copy.parameter_examples = parameter_examples if parameter_examples
|
|
118
|
+
|
|
119
|
+
copy.query_options(prefix:, version: referenced.name)
|
|
120
|
+
end
|
|
90
121
|
end
|
|
91
122
|
end
|
|
92
123
|
end
|