openapi_parameters 0.10.0 → 0.12.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 +8 -0
- data/lib/openapi_parameters/content_parsers.rb +57 -0
- data/lib/openapi_parameters/cookie.rb +1 -1
- data/lib/openapi_parameters/header.rb +1 -1
- data/lib/openapi_parameters/parameter.rb +10 -2
- data/lib/openapi_parameters/path.rb +1 -1
- data/lib/openapi_parameters/query.rb +13 -10
- data/lib/openapi_parameters/unpackers.rb +113 -0
- data/lib/openapi_parameters/version.rb +1 -1
- data/lib/openapi_parameters.rb +3 -1
- metadata +3 -2
- data/lib/openapi_parameters/unpacker.rb +0 -73
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 69f716831414a1780a62e1d78b4405f3e9bc293f8dea9dcd9d56330c56180322
|
|
4
|
+
data.tar.gz: 4621c4705857aa76cdf67c3f0275d4e515b63fe8429fd9de406174dd04625f23
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cc2fb07a22b21031c311271166a204929170fd678385e0f69e00af18c98109550709da12bc9cd625a456cff8cd93a887c2d4b0eabb8c7850765e6095b4cf53b1
|
|
7
|
+
data.tar.gz: f0d679df5b595877fc2033e6ee68014ef108fe13b1c9c128f423220397d79e9aba9f1cf2a5feac4bb63e6ba5afe7b59ca46c97373fcba0102e2e455f94cad960
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.12.0] - 2026-05-18
|
|
4
|
+
|
|
5
|
+
- Add support for parameters defined with a `content` field. Values for media types matching `application/json` or `*+json` are decoded with `JSON.parse` before conversion. Works for query, path, header, and cookie parameters.
|
|
6
|
+
|
|
7
|
+
## [0.11.0] - 2025-11-30
|
|
8
|
+
|
|
9
|
+
- Changed: Passing a plain query string like `filter=ada` to a`deepObject` parameter instead of using the brackets syntax `filter[name]=ada` no longer results in an empty object, but in the plain string value (like `{ filter: 'ada'} `), which will make request validation fail.
|
|
10
|
+
|
|
3
11
|
## [0.10.0] - 2025-11-23
|
|
4
12
|
|
|
5
13
|
- remove `OpenapiParameters::Parameter#definition` to clean up interface
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module OpenapiParameters
|
|
6
|
+
# Registry of content parsers keyed by media type.
|
|
7
|
+
#
|
|
8
|
+
# A parser is a callable that takes a raw string and returns the parsed value.
|
|
9
|
+
# It should `throw :skip, value` if the input cannot be parsed, so the
|
|
10
|
+
# parameter falls through gracefully at the location class level.
|
|
11
|
+
#
|
|
12
|
+
# Built-in: `application/json` and any `*+json` subtype.
|
|
13
|
+
#
|
|
14
|
+
# To register your own:
|
|
15
|
+
#
|
|
16
|
+
# OpenapiParameters::ContentParsers.register('application/xml', ->(v) { ... })
|
|
17
|
+
module ContentParsers
|
|
18
|
+
@parsers = []
|
|
19
|
+
|
|
20
|
+
class << self
|
|
21
|
+
attr_reader :parsers
|
|
22
|
+
|
|
23
|
+
# @param matcher [String, Regexp] exact media type or a pattern.
|
|
24
|
+
# @param parser [#call] callable that takes the raw string and returns the parsed value.
|
|
25
|
+
def register(matcher, parser)
|
|
26
|
+
parsers.reject! { |existing, _| existing == matcher }
|
|
27
|
+
parsers << [matcher, parser]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @param media_type [String, nil]
|
|
31
|
+
# @return [#call, nil] the parser, or nil if none is registered.
|
|
32
|
+
def [](media_type)
|
|
33
|
+
return nil if media_type.nil?
|
|
34
|
+
|
|
35
|
+
parsers.each do |matcher, parser|
|
|
36
|
+
return parser if match?(matcher, media_type)
|
|
37
|
+
end
|
|
38
|
+
nil
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def match?(matcher, media_type)
|
|
44
|
+
case matcher
|
|
45
|
+
when Regexp then matcher.match?(media_type)
|
|
46
|
+
else matcher == media_type
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
register(%r{\A[\w.+-]+/(?:[\w.-]+\+)?json\z}i, lambda do |value|
|
|
52
|
+
JSON.parse(value)
|
|
53
|
+
rescue JSON::ParserError
|
|
54
|
+
throw :skip, value
|
|
55
|
+
end)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -17,7 +17,7 @@ module OpenapiParameters
|
|
|
17
17
|
next unless cookies.key?(parameter.name)
|
|
18
18
|
|
|
19
19
|
result[parameter.name] = catch :skip do
|
|
20
|
-
value =
|
|
20
|
+
value = parameter.unpack(cookies[parameter.name])
|
|
21
21
|
@convert ? parameter.convert(value) : value
|
|
22
22
|
end
|
|
23
23
|
end
|
|
@@ -16,7 +16,7 @@ module OpenapiParameters
|
|
|
16
16
|
next unless headers.key?(parameter.name)
|
|
17
17
|
|
|
18
18
|
result[parameter.name] = catch :skip do
|
|
19
|
-
value =
|
|
19
|
+
value = parameter.unpack(headers[parameter.name])
|
|
20
20
|
@convert ? parameter.convert(value) : value
|
|
21
21
|
end
|
|
22
22
|
end
|
|
@@ -11,10 +11,18 @@ module OpenapiParameters
|
|
|
11
11
|
@is_deep_object = style == 'deepObject'
|
|
12
12
|
@converter = Converters[schema]
|
|
13
13
|
check_supported!
|
|
14
|
+
@unpacker = Unpackers.find(self)
|
|
15
|
+
@bracket_array = (array? && @name&.end_with?(EMPTY_BRACKETS)) || false
|
|
14
16
|
end
|
|
15
17
|
|
|
16
18
|
attr_reader :name
|
|
17
|
-
private attr_reader :definition
|
|
19
|
+
private attr_reader :definition, :unpacker
|
|
20
|
+
|
|
21
|
+
def unpack(value)
|
|
22
|
+
return value if value.nil?
|
|
23
|
+
|
|
24
|
+
unpacker.call(value)
|
|
25
|
+
end
|
|
18
26
|
|
|
19
27
|
def convert(value)
|
|
20
28
|
@converter.call(value)
|
|
@@ -57,7 +65,7 @@ module OpenapiParameters
|
|
|
57
65
|
private_constant :EMPTY_BRACKETS
|
|
58
66
|
|
|
59
67
|
def bracket_array?
|
|
60
|
-
@bracket_array
|
|
68
|
+
@bracket_array
|
|
61
69
|
end
|
|
62
70
|
|
|
63
71
|
def object?
|
|
@@ -20,7 +20,7 @@ module OpenapiParameters
|
|
|
20
20
|
next unless path_params.key?(parameter.name)
|
|
21
21
|
|
|
22
22
|
result[parameter.name] = catch :skip do
|
|
23
|
-
value =
|
|
23
|
+
value = parameter.unpack(path_params[parameter.name])
|
|
24
24
|
@convert ? parameter.convert(value) : value
|
|
25
25
|
end
|
|
26
26
|
end
|
|
@@ -12,18 +12,26 @@ module OpenapiParameters
|
|
|
12
12
|
@parameters = parameters.map { Parameter.new(_1) }
|
|
13
13
|
@convert = convert
|
|
14
14
|
@remove_array_brackets = rack_array_compat
|
|
15
|
+
@deep_object_properties = @parameters.each_with_object({}) do |param, hsh|
|
|
16
|
+
hsh[param.name] = ObjectConverter.get_properties(param.schema) if param.deep_object?
|
|
17
|
+
end
|
|
15
18
|
end
|
|
16
19
|
|
|
17
|
-
def unpack(query_string) # rubocop:disable Metrics/AbcSize
|
|
20
|
+
def unpack(query_string) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
18
21
|
parsed_query = parse_query(query_string)
|
|
19
22
|
parameters.each_with_object({}) do |parameter, result|
|
|
20
23
|
if parameter.deep_object?
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
if parsed_query.key?(parameter.name)
|
|
25
|
+
value = parsed_query[parameter.name]
|
|
26
|
+
else
|
|
27
|
+
value = parse_deep_object(parameter, parsed_query)
|
|
28
|
+
next if value.empty?
|
|
29
|
+
end
|
|
23
30
|
else
|
|
24
31
|
next unless parsed_query.key?(parameter.name)
|
|
25
32
|
|
|
26
|
-
|
|
33
|
+
raw = parsed_query[parameter.name]
|
|
34
|
+
value = catch(:skip) { parameter.unpack(raw) }
|
|
27
35
|
end
|
|
28
36
|
key = if remove_array_brackets && parameter.bracket_array?
|
|
29
37
|
parameter.name.delete_suffix('[]')
|
|
@@ -65,18 +73,13 @@ module OpenapiParameters
|
|
|
65
73
|
end
|
|
66
74
|
end
|
|
67
75
|
|
|
68
|
-
def build_properties_schema(parameter)
|
|
69
|
-
schema = parameter.schema
|
|
70
|
-
ObjectConverter.get_properties(schema) if schema
|
|
71
|
-
end
|
|
72
|
-
|
|
73
76
|
DEEP_PROP = '\[([\w-]+)\]$'
|
|
74
77
|
private_constant :DEEP_PROP
|
|
75
78
|
|
|
76
79
|
def parse_deep_object(parameter, parsed_query)
|
|
77
80
|
name = parameter.name
|
|
78
81
|
prop_regx = /^#{name}#{DEEP_PROP}/
|
|
79
|
-
properties_schema =
|
|
82
|
+
properties_schema = @deep_object_properties[name]
|
|
80
83
|
|
|
81
84
|
parsed_query.each.with_object({}) do |(key, value), result|
|
|
82
85
|
next unless parsed_query.key?(key)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenapiParameters
|
|
4
|
+
# Resolves the per-parameter unpacker once at Parameter construction time.
|
|
5
|
+
# Each unpacker is a callable that takes a raw string (or already-parsed
|
|
6
|
+
# value) and returns the unpacked Ruby value, throwing :skip on unrecoverable
|
|
7
|
+
# parse errors.
|
|
8
|
+
# @visibility private
|
|
9
|
+
module Unpackers
|
|
10
|
+
ARRAY_DELIMITER = {
|
|
11
|
+
'label' => '.',
|
|
12
|
+
'simple' => ',',
|
|
13
|
+
'form' => ',',
|
|
14
|
+
'pipeDelimited' => '|',
|
|
15
|
+
'spaceDelimited' => ' '
|
|
16
|
+
}.freeze
|
|
17
|
+
private_constant :ARRAY_DELIMITER
|
|
18
|
+
|
|
19
|
+
PREFIXED_STYLES = %w[label matrix].freeze
|
|
20
|
+
private_constant :PREFIXED_STYLES
|
|
21
|
+
|
|
22
|
+
OBJECT_EXPLODE_SPLITTER = Regexp.union(',', '=').freeze
|
|
23
|
+
private_constant :OBJECT_EXPLODE_SPLITTER
|
|
24
|
+
|
|
25
|
+
PassThrough = ->(value) { value }
|
|
26
|
+
|
|
27
|
+
DelimitedArray = Data.define(:delimiter, :strip_prefix) do
|
|
28
|
+
def call(value)
|
|
29
|
+
return value if value.is_a?(::Array)
|
|
30
|
+
return value if value.empty?
|
|
31
|
+
|
|
32
|
+
value = value[1..] if strip_prefix
|
|
33
|
+
value.split(delimiter)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
MatrixArray = Data.define(:name, :explode) do
|
|
38
|
+
def call(value)
|
|
39
|
+
return value if value.is_a?(::Array)
|
|
40
|
+
return value if value.empty?
|
|
41
|
+
|
|
42
|
+
result = Rack::Utils.parse_query(value, ';')[name]
|
|
43
|
+
explode ? result : result.split(',')
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
ExplodeFormObject = lambda do |value|
|
|
48
|
+
entries = value.split(OBJECT_EXPLODE_SPLITTER)
|
|
49
|
+
throw :skip, value if entries.length.odd?
|
|
50
|
+
|
|
51
|
+
Hash[*entries]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
DelimitedObject = Data.define(:delimiter) do
|
|
55
|
+
def call(value)
|
|
56
|
+
entries = value.split(delimiter)
|
|
57
|
+
throw :skip, value if entries.length.odd?
|
|
58
|
+
|
|
59
|
+
Hash[*entries]
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
ExplodePathObject = ->(value) { Rack::Utils.parse_query(value, ',') }
|
|
64
|
+
|
|
65
|
+
NonExplodePathObject = Data.define(:array_unpacker) do
|
|
66
|
+
def call(value)
|
|
67
|
+
array = array_unpacker.call(value)
|
|
68
|
+
throw :skip, value if array.length.odd?
|
|
69
|
+
|
|
70
|
+
Hash[*array]
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
class << self
|
|
75
|
+
def find(parameter)
|
|
76
|
+
return find_media_type(parameter) if parameter.media_type
|
|
77
|
+
return find_array(parameter) if parameter.array?
|
|
78
|
+
return find_object(parameter) if parameter.object?
|
|
79
|
+
|
|
80
|
+
PassThrough
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def find_media_type(parameter)
|
|
86
|
+
ContentParsers[parameter.media_type] || PassThrough
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def find_array(parameter)
|
|
90
|
+
style = parameter.style
|
|
91
|
+
return MatrixArray.new(name: parameter.name, explode: parameter.explode?) if style == 'matrix'
|
|
92
|
+
|
|
93
|
+
DelimitedArray.new(
|
|
94
|
+
delimiter: ARRAY_DELIMITER[style],
|
|
95
|
+
strip_prefix: PREFIXED_STYLES.include?(style)
|
|
96
|
+
)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def find_object(parameter)
|
|
100
|
+
return find_path_object(parameter) if parameter.location == 'path'
|
|
101
|
+
return ExplodeFormObject if parameter.explode?
|
|
102
|
+
|
|
103
|
+
DelimitedObject.new(delimiter: ARRAY_DELIMITER[parameter.style])
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def find_path_object(parameter)
|
|
107
|
+
return ExplodePathObject if parameter.explode?
|
|
108
|
+
|
|
109
|
+
NonExplodePathObject.new(array_unpacker: find_array(parameter))
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
data/lib/openapi_parameters.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'json'
|
|
3
4
|
require 'rack'
|
|
4
5
|
require_relative 'openapi_parameters/converters'
|
|
5
6
|
require_relative 'openapi_parameters/converter'
|
|
@@ -8,10 +9,11 @@ require_relative 'openapi_parameters/error'
|
|
|
8
9
|
require_relative 'openapi_parameters/header'
|
|
9
10
|
require_relative 'openapi_parameters/headers_hash'
|
|
10
11
|
require_relative 'openapi_parameters/not_supported_error'
|
|
12
|
+
require_relative 'openapi_parameters/content_parsers'
|
|
13
|
+
require_relative 'openapi_parameters/unpackers'
|
|
11
14
|
require_relative 'openapi_parameters/parameter'
|
|
12
15
|
require_relative 'openapi_parameters/path'
|
|
13
16
|
require_relative 'openapi_parameters/query'
|
|
14
|
-
require_relative 'openapi_parameters/unpacker'
|
|
15
17
|
|
|
16
18
|
# OpenapiParameters is a gem that parses OpenAPI parameters from Rack
|
|
17
19
|
module OpenapiParameters
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: openapi_parameters
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.12.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andreas Haller
|
|
@@ -35,6 +35,7 @@ files:
|
|
|
35
35
|
- README.md
|
|
36
36
|
- lib/openapi_parameters.rb
|
|
37
37
|
- lib/openapi_parameters/array_converter.rb
|
|
38
|
+
- lib/openapi_parameters/content_parsers.rb
|
|
38
39
|
- lib/openapi_parameters/converter.rb
|
|
39
40
|
- lib/openapi_parameters/converters.rb
|
|
40
41
|
- lib/openapi_parameters/cookie.rb
|
|
@@ -46,7 +47,7 @@ files:
|
|
|
46
47
|
- lib/openapi_parameters/parameter.rb
|
|
47
48
|
- lib/openapi_parameters/path.rb
|
|
48
49
|
- lib/openapi_parameters/query.rb
|
|
49
|
-
- lib/openapi_parameters/
|
|
50
|
+
- lib/openapi_parameters/unpackers.rb
|
|
50
51
|
- lib/openapi_parameters/version.rb
|
|
51
52
|
homepage: https://github.com/ahx/openapi_parameters
|
|
52
53
|
licenses:
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module OpenapiParameters
|
|
4
|
-
# Unpacks a parameter value from a string as defined in the OpenAPI specification.
|
|
5
|
-
# @visibility private
|
|
6
|
-
module Unpacker
|
|
7
|
-
class << self
|
|
8
|
-
def unpack_value(parameter, value)
|
|
9
|
-
return value if value.nil?
|
|
10
|
-
return unpack_array(parameter, value) if parameter.array?
|
|
11
|
-
return unpack_object(parameter, value) if parameter.object?
|
|
12
|
-
|
|
13
|
-
value
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def unpack_array(parameter, value)
|
|
17
|
-
return value if value.is_a?(Array)
|
|
18
|
-
return value if value.empty?
|
|
19
|
-
return unpack_matrix(parameter, value) if parameter.style == 'matrix'
|
|
20
|
-
|
|
21
|
-
value = value[1..] if PREFIXED.key?(parameter.style)
|
|
22
|
-
value.split(ARRAY_DELIMITER[parameter.style])
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def unpack_matrix(parameter, value)
|
|
26
|
-
result = Rack::Utils.parse_query(value, ';')[parameter.name]
|
|
27
|
-
return result if parameter.explode?
|
|
28
|
-
|
|
29
|
-
result.split(',')
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
OBJECT_EXPLODE_SPLITTER = Regexp.union(',', '=').freeze
|
|
33
|
-
private_constant :OBJECT_EXPLODE_SPLITTER
|
|
34
|
-
|
|
35
|
-
def unpack_object(parameter, value)
|
|
36
|
-
return unpack_object_path(parameter, value) if parameter.location == 'path'
|
|
37
|
-
|
|
38
|
-
entries =
|
|
39
|
-
if parameter.explode?
|
|
40
|
-
value.split(OBJECT_EXPLODE_SPLITTER)
|
|
41
|
-
else
|
|
42
|
-
value.split(ARRAY_DELIMITER[parameter.style])
|
|
43
|
-
end
|
|
44
|
-
throw :skip, value if entries.length.odd?
|
|
45
|
-
|
|
46
|
-
Hash[*entries]
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def unpack_object_path(parameter, value)
|
|
50
|
-
return Rack::Utils.parse_query(value, ',') if parameter.explode?
|
|
51
|
-
|
|
52
|
-
array = unpack_array(parameter, value)
|
|
53
|
-
throw :skip, value if array.length.odd?
|
|
54
|
-
|
|
55
|
-
Hash[*array]
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
PREFIXED = {
|
|
59
|
-
'label' => '.',
|
|
60
|
-
'matrix' => ';'
|
|
61
|
-
}.freeze
|
|
62
|
-
|
|
63
|
-
ARRAY_DELIMITER = {
|
|
64
|
-
'label' => '.',
|
|
65
|
-
'simple' => ',',
|
|
66
|
-
'form' => ',',
|
|
67
|
-
'pipeDelimited' => '|',
|
|
68
|
-
'spaceDelimited' => ' '
|
|
69
|
-
}.freeze
|
|
70
|
-
private_constant :ARRAY_DELIMITER
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
end
|