verquest 0.6.3 → 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 +47 -0
- data/README.md +324 -51
- 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 +34 -15
- 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 +116 -10
- data/lib/verquest/properties/collection.rb +51 -18
- data/lib/verquest/properties/const.rb +5 -4
- data/lib/verquest/properties/enum.rb +1 -5
- data/lib/verquest/properties/field.rb +1 -0
- 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 +86 -54
- data/lib/verquest/properties/reference.rb +49 -37
- 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 +43 -8
|
@@ -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
|
|
@@ -14,12 +14,14 @@ module Verquest
|
|
|
14
14
|
# @param value [Object] The fixed value of the constant (can be any scalar value)
|
|
15
15
|
# @param map [Object, nil] Optional mapping information
|
|
16
16
|
# @param required [Boolean, Array<Symbol>] Whether this property is required, or array of dependency names (can be overridden by custom type)
|
|
17
|
+
# @param nullable [Boolean] Whether this property can be null
|
|
17
18
|
# @param schema_options [Hash] Additional JSON schema options for this property
|
|
18
|
-
def initialize(name:, value:, map: nil, required: false, **schema_options)
|
|
19
|
+
def initialize(name:, value:, map: nil, required: false, nullable: false, **schema_options)
|
|
19
20
|
@name = name.to_s
|
|
20
21
|
@value = value
|
|
21
22
|
@map = map
|
|
22
23
|
@required = required
|
|
24
|
+
@nullable = nullable
|
|
23
25
|
@schema_options = schema_options&.transform_keys(&:to_s)
|
|
24
26
|
end
|
|
25
27
|
|
|
@@ -28,9 +30,8 @@ module Verquest
|
|
|
28
30
|
# @return [Hash] The schema definition for this constant
|
|
29
31
|
def to_schema
|
|
30
32
|
{
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}.merge(schema_options)
|
|
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
|
|
|
@@ -26,15 +26,11 @@ module Verquest
|
|
|
26
26
|
raise ArgumentError, "Use const for a single value" if values.length == 1
|
|
27
27
|
|
|
28
28
|
@name = name.to_s
|
|
29
|
-
@values = values
|
|
29
|
+
@values = nullable ? values | [nil] : values
|
|
30
30
|
@required = required
|
|
31
31
|
@nullable = nullable
|
|
32
32
|
@map = map
|
|
33
33
|
@schema_options = schema_options&.transform_keys(&:to_s)
|
|
34
|
-
|
|
35
|
-
if nullable && !values.include?("null")
|
|
36
|
-
values << "null"
|
|
37
|
-
end
|
|
38
34
|
end
|
|
39
35
|
|
|
40
36
|
# Generate JSON schema definition for this enum
|
|
@@ -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,10 +30,10 @@ 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
|
-
# JSON Schema for null type, used when nullable is true
|
|
35
|
-
NULL_TYPE_SCHEMA = {"type" => "null"}.freeze
|
|
36
|
-
|
|
37
37
|
# @return [String, nil] The discriminator property name for schema selection
|
|
38
38
|
attr_reader :discriminator
|
|
39
39
|
|
|
@@ -75,7 +75,7 @@ module Verquest
|
|
|
75
75
|
# @return [Hash] The schema definition with oneOf array and optional discriminator
|
|
76
76
|
def to_schema
|
|
77
77
|
freeze_schemas
|
|
78
|
-
wrap_schema(build_schema_with_refs)
|
|
78
|
+
wrap_schema(nullable_schema(build_schema_with_refs))
|
|
79
79
|
end
|
|
80
80
|
|
|
81
81
|
# Generate validation schema for this oneOf property
|
|
@@ -87,7 +87,7 @@ module Verquest
|
|
|
87
87
|
# @return [Hash] The validation schema with inline schema definitions
|
|
88
88
|
def to_validation_schema(version: nil)
|
|
89
89
|
freeze_schemas
|
|
90
|
-
wrap_schema(build_validation_schema(version: version))
|
|
90
|
+
wrap_schema(nullable_schema(build_validation_schema(version: version)))
|
|
91
91
|
end
|
|
92
92
|
|
|
93
93
|
# Create mapping for this oneOf property
|
|
@@ -116,8 +116,33 @@ module Verquest
|
|
|
116
116
|
|
|
117
117
|
build_variant_mappings(mapping, source_prefix, target_prefix, version)
|
|
118
118
|
store_discriminator_path(mapping, source_prefix)
|
|
119
|
-
store_variant_schemas(mapping, version) unless discriminator
|
|
120
|
-
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
|
+
))
|
|
121
146
|
end
|
|
122
147
|
|
|
123
148
|
# Returns validation schemas for all variants
|
|
@@ -194,7 +219,7 @@ module Verquest
|
|
|
194
219
|
# Builds variant mappings for each schema option
|
|
195
220
|
#
|
|
196
221
|
# Handles both Reference and Object schemas:
|
|
197
|
-
# - Reference:
|
|
222
|
+
# - Reference: maps the referenced request's properties under the oneOf path
|
|
198
223
|
# - Object: builds mapping from child properties directly
|
|
199
224
|
#
|
|
200
225
|
# @param mapping [Hash] The mapping hash to populate
|
|
@@ -214,6 +239,9 @@ module Verquest
|
|
|
214
239
|
|
|
215
240
|
# Builds mapping for a Reference variant
|
|
216
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
|
+
#
|
|
217
245
|
# @param mapping [Hash] The mapping hash to populate
|
|
218
246
|
# @param schema [Verquest::Properties::Reference] The reference schema
|
|
219
247
|
# @param source_prefix [Array<String>] Source path prefix
|
|
@@ -221,15 +249,10 @@ module Verquest
|
|
|
221
249
|
# @param version [String, nil] The version for schema resolution
|
|
222
250
|
# @return [void]
|
|
223
251
|
def build_reference_variant_mapping(mapping, schema, source_prefix, target_prefix, version)
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
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] = {}
|
|
227
254
|
|
|
228
|
-
|
|
229
|
-
reference_mapping,
|
|
230
|
-
source_prefix,
|
|
231
|
-
variant_target_prefix
|
|
232
|
-
)
|
|
255
|
+
add_request_mapping(schema.send(:from), key_prefix: source_prefix, value_prefix: variant_target_prefix, mapping: variant_mapping, version:)
|
|
233
256
|
end
|
|
234
257
|
|
|
235
258
|
# Builds mapping for an inline Object variant
|
|
@@ -245,34 +268,34 @@ module Verquest
|
|
|
245
268
|
# @param version [String, nil] The version for schema resolution
|
|
246
269
|
# @return [void]
|
|
247
270
|
def build_object_variant_mapping(mapping, schema, source_prefix, target_prefix, version)
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
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] = {}
|
|
251
273
|
|
|
252
|
-
# Build mapping from object's child properties
|
|
253
274
|
# Properties are mapped directly under source_prefix (not source_prefix + object_name)
|
|
254
|
-
# to match how Reference variants work
|
|
255
|
-
schema.
|
|
256
|
-
property
|
|
257
|
-
key_prefix: source_prefix,
|
|
258
|
-
value_prefix: variant_target_prefix,
|
|
259
|
-
mapping: object_mapping,
|
|
260
|
-
version: version
|
|
261
|
-
)
|
|
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:)
|
|
262
278
|
end
|
|
263
|
-
|
|
264
|
-
mapping[schema.name] = object_mapping
|
|
265
279
|
end
|
|
266
280
|
|
|
267
|
-
#
|
|
281
|
+
# Adds a variant's properties to its layout, directly under the oneOf path
|
|
268
282
|
#
|
|
269
|
-
# @param
|
|
270
|
-
# @param source_prefix [Array<String>]
|
|
271
|
-
# @param target_prefix [Array<String>]
|
|
272
|
-
# @
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
|
276
299
|
end
|
|
277
300
|
end
|
|
278
301
|
|
|
@@ -298,11 +321,13 @@ module Verquest
|
|
|
298
321
|
# schemas to determine which variant matches the input data.
|
|
299
322
|
#
|
|
300
323
|
# @param mapping [Hash] The mapping hash to update
|
|
324
|
+
# @param source_prefix [Array<String>] The source path prefix, ending with the oneOf name
|
|
301
325
|
# @param version [String, nil] The version for schema resolution
|
|
302
326
|
# @return [void]
|
|
303
|
-
def store_variant_schemas(mapping, version)
|
|
327
|
+
def store_variant_schemas(mapping, source_prefix, version)
|
|
304
328
|
mapping["_variant_schemas"] = variant_schemas(version: version)
|
|
305
|
-
|
|
329
|
+
# An unnamed oneOf is only at the root when nothing references it
|
|
330
|
+
mapping["_variant_path"] = source_prefix.join("/") unless source_prefix.empty?
|
|
306
331
|
end
|
|
307
332
|
|
|
308
333
|
# Stores nullable metadata in the mapping
|
|
@@ -311,13 +336,14 @@ module Verquest
|
|
|
311
336
|
# without attempting variant resolution.
|
|
312
337
|
#
|
|
313
338
|
# @param mapping [Hash] The mapping hash to update
|
|
339
|
+
# @param source_prefix [Array<String>] The source path prefix, ending with the oneOf name
|
|
314
340
|
# @param target_prefix [Array<String>] The target path prefix for the oneOf property
|
|
315
341
|
# @return [void]
|
|
316
|
-
def store_nullable_metadata(mapping, target_prefix:)
|
|
342
|
+
def store_nullable_metadata(mapping, source_prefix:, target_prefix:)
|
|
317
343
|
mapping["_nullable"] = true
|
|
318
|
-
return if
|
|
344
|
+
return if source_prefix.empty?
|
|
319
345
|
|
|
320
|
-
mapping["_nullable_path"] =
|
|
346
|
+
mapping["_nullable_path"] = source_prefix.join("/")
|
|
321
347
|
mapping["_nullable_target_path"] = target_prefix.join("/")
|
|
322
348
|
end
|
|
323
349
|
|
|
@@ -372,23 +398,23 @@ module Verquest
|
|
|
372
398
|
|
|
373
399
|
# Builds the validation schema structure with inline definitions
|
|
374
400
|
#
|
|
375
|
-
#
|
|
376
|
-
#
|
|
377
|
-
#
|
|
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.
|
|
378
404
|
#
|
|
379
405
|
# @param version [String, nil] The version to generate validation schema for
|
|
380
|
-
# @return [Hash] Validation schema with oneOf array
|
|
406
|
+
# @return [Hash] Validation schema with oneOf array
|
|
381
407
|
def build_validation_schema(version:)
|
|
382
|
-
{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
|
|
383
411
|
end
|
|
384
412
|
|
|
385
413
|
# Collects $ref schema references for all variants
|
|
386
414
|
#
|
|
387
415
|
# @return [Array<Hash>] Array of schema references
|
|
388
416
|
def collect_schema_refs
|
|
389
|
-
|
|
390
|
-
refs << NULL_TYPE_SCHEMA if nullable
|
|
391
|
-
refs
|
|
417
|
+
schemas.values.map { |schema| schema.to_schema[schema.name] }
|
|
392
418
|
end
|
|
393
419
|
|
|
394
420
|
# Collects inline schema definitions for all variants
|
|
@@ -396,9 +422,15 @@ module Verquest
|
|
|
396
422
|
# @param version [String, nil] The version for schema resolution
|
|
397
423
|
# @return [Array<Hash>] Array of inline schema definitions
|
|
398
424
|
def collect_inline_schemas(version)
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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] }
|
|
402
434
|
end
|
|
403
435
|
|
|
404
436
|
# Adds discriminator information to the schema if present
|