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
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Verquest
|
|
4
|
+
module Conditions
|
|
5
|
+
module Rules
|
|
6
|
+
# Base class for rules that constrain the value of a single property
|
|
7
|
+
#
|
|
8
|
+
# Inside `given`, the rule also requires the property. Without that,
|
|
9
|
+
# `if: {properties: {kind: {const: "x"}}}` would match when `kind` is missing.
|
|
10
|
+
#
|
|
11
|
+
# @abstract Subclass and pass the property schema to {#initialize}
|
|
12
|
+
class PropertyRule
|
|
13
|
+
# Initialize a new property rule
|
|
14
|
+
#
|
|
15
|
+
# @param name [Symbol, String] The property the rule constrains
|
|
16
|
+
# @param schema [Hash] The schema the property value must match
|
|
17
|
+
def initialize(name, schema)
|
|
18
|
+
@name = name.to_s
|
|
19
|
+
@schema = schema
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Returns the names of the properties the rule uses
|
|
23
|
+
#
|
|
24
|
+
# @return [Array<String>] The constrained property name
|
|
25
|
+
def property_names
|
|
26
|
+
[name]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Builds the schema fragment for this rule
|
|
30
|
+
#
|
|
31
|
+
# @param in_given [Boolean] Whether the rule is used inside `given`, which also requires the property
|
|
32
|
+
# @return [Hash] The schema fragment
|
|
33
|
+
def to_schema(in_given: false)
|
|
34
|
+
fragment = {"properties" => {name => schema}}
|
|
35
|
+
in_given ? {"required" => [name]}.merge(fragment) : fragment
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
# @!attribute [r] name
|
|
41
|
+
# @return [String] The constrained property name
|
|
42
|
+
# @!attribute [r] schema
|
|
43
|
+
# @return [Hash] The schema the property value must match
|
|
44
|
+
attr_reader :name, :schema
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Verquest
|
|
4
|
+
module Conditions
|
|
5
|
+
module Rules
|
|
6
|
+
# Requires a property to match arbitrary schema options
|
|
7
|
+
#
|
|
8
|
+
# @example
|
|
9
|
+
# Satisfies.new(:age, {minimum: 18}).to_schema(in_given: false)
|
|
10
|
+
# # => {"properties" => {"age" => {"minimum" => 18}}}
|
|
11
|
+
class Satisfies < PropertyRule
|
|
12
|
+
# Initialize a new Satisfies rule
|
|
13
|
+
#
|
|
14
|
+
# @param name [Symbol, String] The property the rule constrains
|
|
15
|
+
# @param schema_options [Hash] Schema options (already camelized) the property value must match
|
|
16
|
+
def initialize(name, schema_options)
|
|
17
|
+
super(name, schema_options.transform_keys(&:to_s))
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Verquest
|
|
4
|
+
module Conditions
|
|
5
|
+
module Rules
|
|
6
|
+
# Requires a property to hold one of several values
|
|
7
|
+
#
|
|
8
|
+
# @example
|
|
9
|
+
# ValueIn.new(:kind, %w[a b]).to_schema(in_given: false)
|
|
10
|
+
# # => {"properties" => {"kind" => {"enum" => ["a", "b"]}}}
|
|
11
|
+
class ValueIn < PropertyRule
|
|
12
|
+
# Initialize a new ValueIn rule
|
|
13
|
+
#
|
|
14
|
+
# @param name [Symbol, String] The property the rule constrains
|
|
15
|
+
# @param values [Array] The values the property may hold
|
|
16
|
+
def initialize(name, values)
|
|
17
|
+
super(name, {"enum" => values})
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Verquest
|
|
4
|
+
module Conditions
|
|
5
|
+
# Rules used inside `given`, `then_apply`, `else_apply` and `depends_on`
|
|
6
|
+
#
|
|
7
|
+
# Each rule names properties of the scope its condition belongs to and
|
|
8
|
+
# builds a JSON Schema fragment. Every rule responds to `#property_names`
|
|
9
|
+
# (used to catch unknown names) and `#to_schema(in_given:)`.
|
|
10
|
+
#
|
|
11
|
+
# @see Verquest::Conditions::RuleSet Combines the rules of one block
|
|
12
|
+
module Rules
|
|
13
|
+
# This module is a namespace for the rule classes
|
|
14
|
+
# Each rule is defined in its own file under lib/verquest/conditions/rules/
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Verquest
|
|
4
|
+
# Conditional schemas (`if` / `then` / `else`)
|
|
5
|
+
#
|
|
6
|
+
# A condition tests which keys a payload has and what values they hold, and
|
|
7
|
+
# applies one of two branches. Branches can declare properties and rules.
|
|
8
|
+
# Branch properties are listed at the scope level, next to the regular
|
|
9
|
+
# properties, and the branches only get rules (`required`, `name: false`, ...).
|
|
10
|
+
#
|
|
11
|
+
# @example Price adjustment with two variants
|
|
12
|
+
# class PriceAdjustmentRequest < Verquest::Base
|
|
13
|
+
# version "2025-09" do
|
|
14
|
+
# field :effective_from, type: :string, required: true
|
|
15
|
+
#
|
|
16
|
+
# condition :pricing do
|
|
17
|
+
# given { present :percentages }
|
|
18
|
+
# then_apply do
|
|
19
|
+
# object :percentages, required: true do
|
|
20
|
+
# field :unit_types, type: :number
|
|
21
|
+
# end
|
|
22
|
+
# end
|
|
23
|
+
# else_apply do
|
|
24
|
+
# field :percentage, type: :number, required: true
|
|
25
|
+
# end
|
|
26
|
+
# end
|
|
27
|
+
# end
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# @see Verquest::Conditions::Condition
|
|
31
|
+
# @see Verquest::HelperMethods::ConditionalProperties
|
|
32
|
+
module Conditions
|
|
33
|
+
# This module is a namespace for the condition classes
|
|
34
|
+
# Each class is defined in its own file under lib/verquest/conditions/
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -15,6 +15,9 @@ module Verquest
|
|
|
15
15
|
class Configuration
|
|
16
16
|
include Base::HelperClassMethods
|
|
17
17
|
|
|
18
|
+
# JSON Schema versions without the `const` keyword
|
|
19
|
+
VERSIONS_WITHOUT_CONST = %i[draft4 openapi30].freeze
|
|
20
|
+
|
|
18
21
|
# Mapping of supported JSON Schema versions to their implementation classes
|
|
19
22
|
#
|
|
20
23
|
# This constant maps the symbolic names of JSON Schema versions to their
|
|
@@ -58,8 +61,13 @@ module Verquest
|
|
|
58
61
|
# @!attribute [rw] default_additional_properties
|
|
59
62
|
# Controls the default behavior for handling properties not defined in the schema
|
|
60
63
|
# @return [Boolean] false to disallow additional properties (default), true to allow them
|
|
64
|
+
#
|
|
65
|
+
# @!attribute [rw] query_array_explode
|
|
66
|
+
# Default explode value of array properties in query requests (`location :query`)
|
|
67
|
+
# @return [Boolean] true for repeated keys like `status[]=a&status[]=b` (default), false for comma-separated values like `status=a,b`
|
|
61
68
|
attr_accessor :validate_params, :json_schema_version, :validation_error_handling,
|
|
62
|
-
:remove_extra_root_keys, :insert_property_defaults, :default_additional_properties
|
|
69
|
+
:remove_extra_root_keys, :insert_property_defaults, :default_additional_properties,
|
|
70
|
+
:query_array_explode
|
|
63
71
|
|
|
64
72
|
# @!attribute [r] current_version
|
|
65
73
|
# A callable object that returns the current API version to use when not explicitly specified
|
|
@@ -86,6 +94,7 @@ module Verquest
|
|
|
86
94
|
@insert_property_defaults = true
|
|
87
95
|
@custom_field_types = {}
|
|
88
96
|
@default_additional_properties = false
|
|
97
|
+
@query_array_explode = true
|
|
89
98
|
end
|
|
90
99
|
|
|
91
100
|
# Sets the current version strategy using a callable object
|
|
@@ -151,6 +160,15 @@ module Verquest
|
|
|
151
160
|
SCHEMAS[json_schema_version] || raise(ArgumentError, "Unsupported JSON Schema version: #{json_schema_version}")
|
|
152
161
|
end
|
|
153
162
|
|
|
163
|
+
# Checks whether the configured JSON Schema version has the `const` keyword
|
|
164
|
+
#
|
|
165
|
+
# Draft 4 and OpenAPI 3.0 don't, so a constant is written as a one-value `enum` there.
|
|
166
|
+
#
|
|
167
|
+
# @return [Boolean] False for draft 4 and OpenAPI 3.0
|
|
168
|
+
def const_keyword?
|
|
169
|
+
!VERSIONS_WITHOUT_CONST.include?(json_schema_version)
|
|
170
|
+
end
|
|
171
|
+
|
|
154
172
|
# Gets the JSON Schema URI for the configured schema version
|
|
155
173
|
#
|
|
156
174
|
# @return [String] The base URI for the configured JSON Schema version
|
data/lib/verquest/gem_version.rb
CHANGED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Verquest
|
|
4
|
+
module HelperMethods
|
|
5
|
+
# Module that adds conditions (`if` / `then` / `else`) to a scope
|
|
6
|
+
#
|
|
7
|
+
# Included in Version, Properties::Object and Properties::Collection (inline
|
|
8
|
+
# items). The including class must set `@conditions = []` and have a
|
|
9
|
+
# `properties` hash.
|
|
10
|
+
#
|
|
11
|
+
# The branch properties of all conditions are listed next to the regular
|
|
12
|
+
# properties ({#all_properties}), so `additionalProperties` keeps working and
|
|
13
|
+
# the mapping is the flat union of both. The scope schema gets the
|
|
14
|
+
# condition's `if` / `then` / `else` keywords, or one `allOf` entry per
|
|
15
|
+
# condition when there are several.
|
|
16
|
+
module ConditionalProperties
|
|
17
|
+
# Schema options that can't be combined with conditions
|
|
18
|
+
RESERVED_KEYWORDS = %w[if then else allOf].freeze
|
|
19
|
+
|
|
20
|
+
# JSON Schema versions that ignore `if` / `then` / `else` and would accept every payload
|
|
21
|
+
UNSUPPORTED_DIALECTS = %i[draft4 draft6 openapi30].freeze
|
|
22
|
+
|
|
23
|
+
# @!attribute [r] conditions
|
|
24
|
+
# @return [Array<Verquest::Conditions::Condition>] The conditions in declaration order
|
|
25
|
+
attr_reader :conditions
|
|
26
|
+
|
|
27
|
+
# Add a condition to this scope
|
|
28
|
+
#
|
|
29
|
+
# A named condition can be declared only once per scope. To replace an
|
|
30
|
+
# inherited one, call `exclude_conditions` first.
|
|
31
|
+
#
|
|
32
|
+
# @param condition [Verquest::Conditions::Condition] The condition to add
|
|
33
|
+
# @return [Array<Verquest::Conditions::Condition>] The conditions
|
|
34
|
+
# @raise [ArgumentError] If a condition with the same name is already defined
|
|
35
|
+
def add_condition(condition)
|
|
36
|
+
raise ArgumentError, "Condition '#{condition.name}' is already defined" if condition.name && conditions.any? { |other| other.name == condition.name }
|
|
37
|
+
|
|
38
|
+
conditions << condition
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Remove a named condition from this scope
|
|
42
|
+
#
|
|
43
|
+
# @param name [Symbol, String] The condition name
|
|
44
|
+
# @return [Verquest::Conditions::Condition] The removed condition
|
|
45
|
+
# @raise [ArgumentError] If no condition has that name
|
|
46
|
+
def remove_condition(name)
|
|
47
|
+
condition = conditions.find { |candidate| candidate.name == name.to_s }
|
|
48
|
+
raise ArgumentError, "Condition '#{name}' is not defined" unless condition
|
|
49
|
+
|
|
50
|
+
conditions.delete(condition)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Returns the regular properties followed by the branch properties
|
|
54
|
+
#
|
|
55
|
+
# Computed on every call, so it always reflects the current declarations.
|
|
56
|
+
#
|
|
57
|
+
# @return [Hash<String, Verquest::Properties::Base>] All properties listed in the scope schema
|
|
58
|
+
# @raise [ArgumentError] If a branch property clashes with another declaration
|
|
59
|
+
def all_properties
|
|
60
|
+
properties.merge(conditional_properties)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
# Merges the branch properties of all conditions
|
|
66
|
+
#
|
|
67
|
+
# @return [Hash<String, Verquest::Properties::Base>] The branch properties
|
|
68
|
+
# @raise [ArgumentError] If a branch property clashes with another declaration
|
|
69
|
+
def conditional_properties
|
|
70
|
+
Conditions::PropertyMerge.call(conditions.map(&:properties)).tap { |branch_properties| check_property_clashes!(branch_properties) }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Checks that no branch property is also declared on the scope
|
|
74
|
+
#
|
|
75
|
+
# @param branch_properties [Hash<String, Verquest::Properties::Base>] The branch properties
|
|
76
|
+
# @return [void]
|
|
77
|
+
# @raise [ArgumentError] If a branch property is also declared on the scope
|
|
78
|
+
def check_property_clashes!(branch_properties)
|
|
79
|
+
clash = branch_properties.keys.find { |name| properties.key?(name) }
|
|
80
|
+
raise ArgumentError, "Property '#{clash}' is declared both outside and inside a condition" if clash
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Adds the conditional keywords to a scope schema
|
|
84
|
+
#
|
|
85
|
+
# In documentation, a condition declared with `document_as: :one_of`
|
|
86
|
+
# replaces the schema with a oneOf of its branches instead.
|
|
87
|
+
#
|
|
88
|
+
# @param schema [Hash] The scope schema, changed in place
|
|
89
|
+
# @param documentation [Boolean] Whether the schema is the documented one (`to_schema`)
|
|
90
|
+
# @return [Hash] The schema
|
|
91
|
+
# @raise [ArgumentError] If a condition is invalid or the schema already has conditional keywords
|
|
92
|
+
def add_conditions(schema, documentation: false)
|
|
93
|
+
return schema if conditions.empty?
|
|
94
|
+
|
|
95
|
+
validate_conditions(schema)
|
|
96
|
+
return schema.replace(conditions.first.to_one_of_schema(schema)) if documentation && conditions.first.document_as
|
|
97
|
+
|
|
98
|
+
schema.merge!(conditions_schema)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Checks the conditions before they are added to the schema
|
|
102
|
+
#
|
|
103
|
+
# @param schema [Hash] The scope schema
|
|
104
|
+
# @return [void]
|
|
105
|
+
# @raise [ArgumentError] If the JSON Schema version ignores conditions, the schema already
|
|
106
|
+
# has conditional keywords, a condition documented as one_of is not alone, or a condition is invalid
|
|
107
|
+
def validate_conditions(schema)
|
|
108
|
+
dialect = Verquest.configuration.json_schema_version
|
|
109
|
+
raise ArgumentError, "Conditions need json_schema_version draft7 or later, or openapi31 (got #{dialect})" if UNSUPPORTED_DIALECTS.include?(dialect)
|
|
110
|
+
raise ArgumentError, "Raw if, then, else or allOf schema options can not be combined with conditions" if schema.keys.intersect?(RESERVED_KEYWORDS)
|
|
111
|
+
raise ArgumentError, "A condition documented as one_of must be the only condition in its version" if conditions.size > 1 && conditions.any?(&:document_as)
|
|
112
|
+
|
|
113
|
+
conditions.each { |condition| condition.validate!(all_properties.keys) }
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Builds the conditional keywords
|
|
117
|
+
#
|
|
118
|
+
# @return [Hash] `if` / `then` / `else` for one condition, `allOf` for several
|
|
119
|
+
def conditions_schema
|
|
120
|
+
return conditions.first.to_schema if conditions.one?
|
|
121
|
+
|
|
122
|
+
{"allOf" => conditions.map(&:to_schema)}
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Verquest
|
|
4
|
+
module HelperMethods
|
|
5
|
+
# Checks that an unnamed oneOf is the only property in its scope
|
|
6
|
+
#
|
|
7
|
+
# An unnamed oneOf stands for the whole scope: the version, or a
|
|
8
|
+
# collection item. Its variants have nowhere to go next to other
|
|
9
|
+
# properties, so that is rejected when the properties are declared.
|
|
10
|
+
module UnnamedOneOf
|
|
11
|
+
# The error raised for an unnamed oneOf next to other properties
|
|
12
|
+
MESSAGE = "An unnamed one_of must be the only property in its scope. Give it a name: to declare it next to other properties"
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
# @param property [Verquest::Properties::Base] The property being added
|
|
17
|
+
# @return [void]
|
|
18
|
+
# @raise [ArgumentError] If an unnamed oneOf would share the scope with other properties
|
|
19
|
+
def check_unnamed_one_of(property)
|
|
20
|
+
# A property with the same name replaces the existing one, as in an inheriting version
|
|
21
|
+
others = properties.values.reject { |other| other.name.to_s == property.name.to_s }
|
|
22
|
+
return if others.empty?
|
|
23
|
+
return unless unnamed_one_of?(property) || others.any? { |other| unnamed_one_of?(other) }
|
|
24
|
+
|
|
25
|
+
raise ArgumentError, MESSAGE
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @param property [Verquest::Properties::Base] The property
|
|
29
|
+
# @return [Boolean] True for a oneOf without a name
|
|
30
|
+
def unnamed_one_of?(property)
|
|
31
|
+
property.is_a?(Properties::OneOf) && property.name.nil?
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -15,6 +15,9 @@ module Verquest
|
|
|
15
15
|
# required: true
|
|
16
16
|
# )
|
|
17
17
|
class Array < Base
|
|
18
|
+
# @return [Boolean] In query requests, whether items are sent as repeated keys (true) or comma-separated (false)
|
|
19
|
+
attr_reader :explode
|
|
20
|
+
|
|
18
21
|
# Initialize a new Array property
|
|
19
22
|
#
|
|
20
23
|
# @param name [String, Symbol] The name of the property
|
|
@@ -22,11 +25,12 @@ module Verquest
|
|
|
22
25
|
# @param map [String, nil] The mapping path for this property (nil for no explicit mapping)
|
|
23
26
|
# @param required [Boolean, Array<Symbol>] Whether this property is required, or array of dependency names
|
|
24
27
|
# @param nullable [Boolean] Whether this property can be null
|
|
28
|
+
# @param explode [Boolean, nil] In query requests, whether items are sent as repeated keys (true) or comma-separated (false); nil uses the configuration
|
|
25
29
|
# @param item_schema_options [Hash] Additional JSON schema options for the array items (merged with custom type options)
|
|
26
30
|
# @param schema_options [Hash] Additional JSON schema options for the array property itself
|
|
27
31
|
# @raise [ArgumentError] If type is not one of the allowed types (default or custom)
|
|
28
32
|
# @raise [ArgumentError] If attempting to map an array to the root
|
|
29
|
-
def initialize(name:, type:, map: nil, required: false, nullable: false, item_schema_options: {}, **schema_options)
|
|
33
|
+
def initialize(name:, type:, map: nil, required: false, nullable: false, explode: nil, item_schema_options: {}, **schema_options)
|
|
30
34
|
raise ArgumentError, "Type must be one of #{allowed_types.join(", ")}" unless allowed_types.include?(type.to_s)
|
|
31
35
|
raise ArgumentError, "You can not map array to the root" if map == "/"
|
|
32
36
|
|
|
@@ -46,6 +50,7 @@ module Verquest
|
|
|
46
50
|
@map = map
|
|
47
51
|
@required = required
|
|
48
52
|
@nullable = nullable
|
|
53
|
+
@explode = explode.nil? ? Verquest.configuration.query_array_explode : explode
|
|
49
54
|
@schema_options = schema_options&.transform_keys(&:to_s)
|
|
50
55
|
|
|
51
56
|
@type = if nullable
|
|
@@ -78,6 +83,15 @@ module Verquest
|
|
|
78
83
|
mapping[(key_prefix + [name]).join("/")] = mapping_value_key(value_prefix:)
|
|
79
84
|
end
|
|
80
85
|
|
|
86
|
+
# Returns this array's explode value and parameter examples
|
|
87
|
+
#
|
|
88
|
+
# @param prefix [Array<String>] Path of the parent property
|
|
89
|
+
# @param version [String] The version to resolve references with
|
|
90
|
+
# @return [Hash{Array<String> => Hash}] The options of this array, by path
|
|
91
|
+
def query_options(prefix:, version:)
|
|
92
|
+
{prefix + [name] => {explode:, examples: parameter_examples}.compact}
|
|
93
|
+
end
|
|
94
|
+
|
|
81
95
|
private
|
|
82
96
|
|
|
83
97
|
attr_reader :type, :item_type, :schema_options, :item_schema_options
|
|
@@ -21,7 +21,9 @@ module Verquest
|
|
|
21
21
|
# @return [Boolean] Whether this property is required
|
|
22
22
|
# @!attribute [rw] map
|
|
23
23
|
# @return [String, nil] The mapping path for this property
|
|
24
|
-
|
|
24
|
+
# @!attribute [rw] parameter_examples
|
|
25
|
+
# @return [Hash, nil] Named OpenAPI Example Objects for this property's query parameter, kept out of the JSON schemas
|
|
26
|
+
attr_accessor :name, :required, :map, :parameter_examples
|
|
25
27
|
|
|
26
28
|
# Adds a child property to this property
|
|
27
29
|
# @abstract
|
|
@@ -58,6 +60,54 @@ module Verquest
|
|
|
58
60
|
raise NoMethodError
|
|
59
61
|
end
|
|
60
62
|
|
|
63
|
+
# Adds this property to a transformer layout
|
|
64
|
+
#
|
|
65
|
+
# This is the layout of a scalar property: one source and target pair.
|
|
66
|
+
# Objects, collections, references and oneOfs override it.
|
|
67
|
+
#
|
|
68
|
+
# @param key_prefix [Array<String>] Prefix for the source key
|
|
69
|
+
# @param value_prefix [Array<String>] Prefix for the target value
|
|
70
|
+
# @param layout [Verquest::Transformer::Layout] The layout to add to
|
|
71
|
+
# @param version [String, nil] The version to create the layout for
|
|
72
|
+
# @return [void]
|
|
73
|
+
def layout(key_prefix:, value_prefix:, layout:, version:)
|
|
74
|
+
layout.add_pair((key_prefix + [name]).join("/"), mapping_value_key(value_prefix:))
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Returns the query parameter options under this property, by path
|
|
78
|
+
#
|
|
79
|
+
# Options are `explode:` for arrays and `examples:` for properties with
|
|
80
|
+
# parameter examples. Paths without options are left out.
|
|
81
|
+
#
|
|
82
|
+
# @param prefix [Array<String>] Path of the parent property
|
|
83
|
+
# @param version [String] The version to resolve references with
|
|
84
|
+
# @return [Hash{Array<String> => Hash}] The options of each property path
|
|
85
|
+
def query_options(prefix:, version:)
|
|
86
|
+
parameter_examples ? {prefix + [name] => {examples: parameter_examples}} : {}
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Returns a copy of this property under another name and map:
|
|
90
|
+
#
|
|
91
|
+
# Used by a reference to a single property, so the property maps as if
|
|
92
|
+
# it were declared where the reference is.
|
|
93
|
+
#
|
|
94
|
+
# @param name [String, Symbol] The new name
|
|
95
|
+
# @param map [String, nil] The new map: option
|
|
96
|
+
# @return [Verquest::Properties::Base] The renamed copy
|
|
97
|
+
def renamed(name:, map:)
|
|
98
|
+
dup.tap { |copy| copy.rename(name, map) }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
protected
|
|
102
|
+
|
|
103
|
+
# @param name [String, Symbol] The new name
|
|
104
|
+
# @param map [String, nil] The new map: option
|
|
105
|
+
# @return [void]
|
|
106
|
+
def rename(name, map)
|
|
107
|
+
@name = name.to_s
|
|
108
|
+
@map = map
|
|
109
|
+
end
|
|
110
|
+
|
|
61
111
|
private
|
|
62
112
|
|
|
63
113
|
# @!attribute [r] nullable
|
|
@@ -75,10 +125,9 @@ module Verquest
|
|
|
75
125
|
|
|
76
126
|
# Determines the mapping target key based on mapping configuration
|
|
77
127
|
# @param value_prefix [Array<String>] Prefix for the target value
|
|
78
|
-
# @param collection [Boolean] Whether this is a collection mapping
|
|
79
128
|
# @return [String] The target mapping key
|
|
80
|
-
def mapping_value_key(value_prefix
|
|
81
|
-
|
|
129
|
+
def mapping_value_key(value_prefix:)
|
|
130
|
+
if map.nil?
|
|
82
131
|
(value_prefix + [name]).join("/")
|
|
83
132
|
elsif map == "/"
|
|
84
133
|
""
|
|
@@ -87,12 +136,6 @@ module Verquest
|
|
|
87
136
|
else
|
|
88
137
|
(value_prefix + map.split("/")).join("/")
|
|
89
138
|
end
|
|
90
|
-
|
|
91
|
-
if collection
|
|
92
|
-
value_key + "[]"
|
|
93
|
-
else
|
|
94
|
-
value_key
|
|
95
|
-
end
|
|
96
139
|
end
|
|
97
140
|
|
|
98
141
|
# Determines the mapping target value prefix based on mapping configuration
|
|
@@ -117,6 +160,57 @@ module Verquest
|
|
|
117
160
|
|
|
118
161
|
value_prefix
|
|
119
162
|
end
|
|
163
|
+
|
|
164
|
+
# Adds the mapping of a child property
|
|
165
|
+
#
|
|
166
|
+
# A oneOf gets its own mapping in the `_oneOfs` array, so its metadata
|
|
167
|
+
# can't clash with another oneOf in the same mapping.
|
|
168
|
+
#
|
|
169
|
+
# @param property [Verquest::Properties::Base] The child property
|
|
170
|
+
# @param mapping [Hash] The mapping hash to be updated
|
|
171
|
+
# @param options [Hash] The key_prefix, value_prefix and version for the child mapping
|
|
172
|
+
# @return [void]
|
|
173
|
+
def add_child_mapping(property, mapping:, **options)
|
|
174
|
+
return property.mapping(mapping:, **options) unless property.is_a?(OneOf)
|
|
175
|
+
|
|
176
|
+
one_of_mapping = {}
|
|
177
|
+
property.mapping(mapping: one_of_mapping, **options)
|
|
178
|
+
(mapping["_oneOfs"] ||= []) << one_of_mapping
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Adds the mappings of a referenced request's properties under the given prefixes
|
|
182
|
+
#
|
|
183
|
+
# Each property maps itself with the prefixes, instead of the finished
|
|
184
|
+
# mapping of the request being copied, so its oneOfs keep their
|
|
185
|
+
# metadata and land in `_oneOfs`.
|
|
186
|
+
#
|
|
187
|
+
# @param request [Class<Verquest::Base>] The referenced request
|
|
188
|
+
# @param key_prefix [Array<String>] Prefix for the source keys
|
|
189
|
+
# @param value_prefix [Array<String>] Prefix for the target values
|
|
190
|
+
# @param mapping [Hash] The mapping hash to be updated
|
|
191
|
+
# @param version [String] The version to create mapping for
|
|
192
|
+
# @return [void]
|
|
193
|
+
def add_request_mapping(request, key_prefix:, value_prefix:, mapping:, version:)
|
|
194
|
+
referenced = request.resolve(version)
|
|
195
|
+
referenced.all_properties.each_value do |property|
|
|
196
|
+
add_child_mapping(property, key_prefix:, value_prefix:, mapping:, version: referenced.name)
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Adds a referenced request's properties to a layout under the given prefixes
|
|
201
|
+
#
|
|
202
|
+
# @param request [Class<Verquest::Base>] The referenced request
|
|
203
|
+
# @param key_prefix [Array<String>] Prefix for the source keys
|
|
204
|
+
# @param value_prefix [Array<String>] Prefix for the target values
|
|
205
|
+
# @param layout [Verquest::Transformer::Layout] The layout to add to
|
|
206
|
+
# @param version [String] The version to create the layout for
|
|
207
|
+
# @return [void]
|
|
208
|
+
def add_request_layout(request, key_prefix:, value_prefix:, layout:, version:)
|
|
209
|
+
referenced = request.resolve(version)
|
|
210
|
+
referenced.all_properties.each_value do |property|
|
|
211
|
+
property.layout(key_prefix:, value_prefix:, layout:, version: referenced.name)
|
|
212
|
+
end
|
|
213
|
+
end
|
|
120
214
|
end
|
|
121
215
|
end
|
|
122
216
|
end
|