openapi_first 1.3.2 → 1.3.5
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 +11 -0
- data/lib/openapi_first/definition/operation.rb +1 -1
- data/lib/openapi_first/definition/path_item.rb +7 -0
- data/lib/openapi_first/definition/path_template.rb +40 -0
- data/lib/openapi_first/definition.rb +3 -6
- data/lib/openapi_first/failure.rb +2 -2
- data/lib/openapi_first/json_refs.rb +89 -79
- data/lib/openapi_first/runtime_response.rb +4 -2
- data/lib/openapi_first/schema.rb +1 -1
- data/lib/openapi_first/version.rb +1 -1
- data/lib/openapi_first.rb +2 -1
- metadata +3 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 631b8fea22128020990af7edb3605d0e842d04b0f876affb4277b3f81b571921
|
4
|
+
data.tar.gz: 3fc61f1ca41bb0d380c188681bad5477c36c6cbeeeec5fabf1700d468987aae5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e2b1210a029a62742928ebf2e285a58d9d48d2a809373cce1652a13f8b538675b37be361c99625d6e0bb6cf70ea58ebbaba7486f8ac73896cf5989b74257cd06
|
7
|
+
data.tar.gz: 0e5d4798b159fd71af8da50703644f71a16cc276bfdafcaff3cf64777042d6ed9b78ac62ba1dfe07cbd1026cf8d359b77124a5abe39d239030c5d5f5b967b12c
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,17 @@
|
|
2
2
|
|
3
3
|
## Unreleased
|
4
4
|
|
5
|
+
## 1.3.5
|
6
|
+
|
7
|
+
- Added support for `/some/{kebab-cased}` path parameters ([#245](https://github.com/ahx/openapi_first/issues/245))
|
8
|
+
|
9
|
+
## 1.3.4
|
10
|
+
|
11
|
+
- Fixed handling "binary" format in optional multipart file uploads
|
12
|
+
- Cache the resolved OAD. This especially makes things run faster in tests.
|
13
|
+
|
14
|
+
## 1.3.3 (yanked)
|
15
|
+
|
5
16
|
## 1.3.2
|
6
17
|
|
7
18
|
### Changed
|
@@ -95,7 +95,7 @@ module OpenapiFirst
|
|
95
95
|
# Returns a unique name for this operation. Used for generating error messages.
|
96
96
|
# @visibility private
|
97
97
|
def name
|
98
|
-
@name ||= "#{method.upcase} #{path}
|
98
|
+
@name ||= "#{method.upcase} #{path}"
|
99
99
|
end
|
100
100
|
|
101
101
|
# Returns the path parameters of the operation.
|
@@ -1,19 +1,26 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'forwardable'
|
3
4
|
require_relative 'operation'
|
5
|
+
require_relative 'path_template'
|
4
6
|
|
5
7
|
module OpenapiFirst
|
6
8
|
class Definition
|
7
9
|
# A pathItem as defined in the OpenAPI document.
|
8
10
|
class PathItem
|
11
|
+
extend Forwardable
|
12
|
+
|
9
13
|
def initialize(path, path_item_object, openapi_version:)
|
10
14
|
@path = path
|
11
15
|
@path_item_object = path_item_object
|
12
16
|
@openapi_version = openapi_version
|
17
|
+
@path_template = PathTemplate.new(path)
|
13
18
|
end
|
14
19
|
|
15
20
|
attr_reader :path
|
16
21
|
|
22
|
+
def_delegator :@path_template, :match
|
23
|
+
|
17
24
|
def operation(request_method)
|
18
25
|
return unless @path_item_object[request_method]
|
19
26
|
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OpenapiFirst
|
4
|
+
class Definition
|
5
|
+
# @visibility private
|
6
|
+
class PathTemplate
|
7
|
+
# See also https://spec.openapis.org/oas/v3.1.0#path-templating
|
8
|
+
TEMPLATE_EXPRESSION = /(\{[^}]+\})/
|
9
|
+
TEMPLATE_EXPRESSION_NAME = /\{([^}]+)\}/
|
10
|
+
ALLOWED_PARAMETER_CHARACTERS = %r{([^/?#]+)}
|
11
|
+
|
12
|
+
def initialize(template)
|
13
|
+
@template = template
|
14
|
+
@names = template.scan(TEMPLATE_EXPRESSION_NAME).flatten
|
15
|
+
@pattern = build_pattern(template)
|
16
|
+
end
|
17
|
+
|
18
|
+
def match(path)
|
19
|
+
return {} if path == @template
|
20
|
+
return if @names.empty?
|
21
|
+
|
22
|
+
matches = path.match(@pattern)
|
23
|
+
return unless matches
|
24
|
+
|
25
|
+
values = matches.captures
|
26
|
+
@names.zip(values).to_h
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def build_pattern(template)
|
32
|
+
parts = template.split(TEMPLATE_EXPRESSION).map! do |part|
|
33
|
+
part.start_with?('{') ? ALLOWED_PARAMETER_CHARACTERS : Regexp.escape(part)
|
34
|
+
end
|
35
|
+
|
36
|
+
%r{^#{parts.join}/?$}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'mustermann'
|
4
3
|
require_relative 'definition/path_item'
|
5
4
|
require_relative 'runtime_request'
|
6
5
|
require_relative 'request_validation/validator'
|
@@ -99,14 +98,12 @@ module OpenapiFirst
|
|
99
98
|
end
|
100
99
|
|
101
100
|
def search_for_path_item(request_path)
|
102
|
-
|
103
|
-
|
104
|
-
path_params = template.params(request_path)
|
101
|
+
path_items.find do |path_item|
|
102
|
+
path_params = path_item.match(request_path)
|
105
103
|
next unless path_params
|
106
|
-
next unless path_params.size == template.names.size
|
107
104
|
|
108
105
|
return [
|
109
|
-
|
106
|
+
path_item,
|
110
107
|
path_params
|
111
108
|
]
|
112
109
|
end
|
@@ -71,8 +71,8 @@ module OpenapiFirst
|
|
71
71
|
private
|
72
72
|
|
73
73
|
def generate_message
|
74
|
-
messages = errors&.take(
|
75
|
-
messages << "... (#{errors.size} errors total)" if errors && errors.size >
|
74
|
+
messages = errors&.take(3)&.map(&:error)
|
75
|
+
messages << "... (#{errors.size} errors total)" if errors && errors.size > 3
|
76
76
|
messages&.join('. ')
|
77
77
|
end
|
78
78
|
end
|
@@ -32,112 +32,122 @@ require 'hana'
|
|
32
32
|
require 'json'
|
33
33
|
require 'yaml'
|
34
34
|
|
35
|
-
module
|
36
|
-
class
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
35
|
+
module OpenapiFirst
|
36
|
+
class FileNotFoundError < StandardError; end
|
37
|
+
|
38
|
+
module JsonRefs
|
39
|
+
class << self
|
40
|
+
def dereference(doc)
|
41
|
+
file_cache = {}
|
42
|
+
Dereferencer.new(Dir.pwd, doc, file_cache).call
|
43
|
+
end
|
41
44
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
45
|
+
def load(filename)
|
46
|
+
doc_dir = File.dirname(filename)
|
47
|
+
doc = Loader.handle(filename)
|
48
|
+
file_cache = {}
|
49
|
+
Dereferencer.new(filename, doc_dir, doc, file_cache).call
|
50
|
+
end
|
47
51
|
end
|
48
|
-
end
|
49
52
|
|
50
|
-
|
51
|
-
|
53
|
+
module LocalRef
|
54
|
+
module_function
|
52
55
|
|
53
|
-
|
54
|
-
|
56
|
+
def call(path:, doc:)
|
57
|
+
Hana::Pointer.new(path[1..]).eval(doc)
|
58
|
+
end
|
55
59
|
end
|
56
|
-
end
|
57
60
|
|
58
|
-
|
59
|
-
|
61
|
+
module Loader
|
62
|
+
module_function
|
60
63
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
+
def handle(filename)
|
65
|
+
body = File.read(filename)
|
66
|
+
return JSON.parse(body) if File.extname(filename) == '.json'
|
64
67
|
|
65
|
-
|
68
|
+
YAML.unsafe_load(body)
|
69
|
+
end
|
66
70
|
end
|
67
|
-
end
|
68
71
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
72
|
+
class Dereferencer
|
73
|
+
def initialize(filename, doc_dir, doc, file_cache)
|
74
|
+
@filename = filename
|
75
|
+
@doc = doc
|
76
|
+
@doc_dir = doc_dir
|
77
|
+
@file_cache = file_cache
|
78
|
+
end
|
75
79
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
80
|
+
def call(doc = @doc, keys = [])
|
81
|
+
if doc.is_a?(Array)
|
82
|
+
doc.each_with_index do |value, idx|
|
83
|
+
call(value, keys + [idx])
|
84
|
+
end
|
85
|
+
elsif doc.is_a?(Hash)
|
86
|
+
if doc.key?('$ref')
|
87
|
+
dereference(keys, doc['$ref'])
|
88
|
+
else
|
89
|
+
doc.each do |key, value|
|
90
|
+
call(value, keys + [key])
|
91
|
+
end
|
87
92
|
end
|
88
93
|
end
|
94
|
+
doc
|
89
95
|
end
|
90
|
-
doc
|
91
|
-
end
|
92
96
|
|
93
|
-
|
97
|
+
private
|
94
98
|
|
95
|
-
|
99
|
+
attr_reader :doc_dir
|
96
100
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
+
def dereference(paths, referenced_path)
|
102
|
+
key = paths.pop
|
103
|
+
target = paths.inject(@doc) do |obj, k|
|
104
|
+
obj[k]
|
105
|
+
end
|
106
|
+
value = follow_referenced_value(referenced_path)
|
107
|
+
target[key] = value
|
101
108
|
end
|
102
|
-
value = follow_referenced_value(referenced_path)
|
103
|
-
target[key] = value
|
104
|
-
end
|
105
109
|
|
106
|
-
|
107
|
-
|
108
|
-
|
110
|
+
def follow_referenced_value(referenced_path)
|
111
|
+
value = referenced_value(referenced_path)
|
112
|
+
return referenced_value(value['$ref']) if value.is_a?(Hash) && value.key?('$ref')
|
109
113
|
|
110
|
-
|
111
|
-
|
114
|
+
value
|
115
|
+
end
|
112
116
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
+
def referenced_value(referenced_path)
|
118
|
+
filepath, pointer = referenced_path.split('#')
|
119
|
+
pointer&.prepend('#')
|
120
|
+
return dereference_local(pointer) if filepath.empty?
|
117
121
|
|
118
|
-
|
119
|
-
|
122
|
+
dereferenced_file = dereference_file(filepath)
|
123
|
+
return dereferenced_file if pointer.nil?
|
120
124
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
125
|
+
LocalRef.call(
|
126
|
+
path: pointer,
|
127
|
+
doc: dereferenced_file
|
128
|
+
)
|
129
|
+
end
|
126
130
|
|
127
|
-
|
128
|
-
|
129
|
-
|
131
|
+
def dereference_local(referenced_path)
|
132
|
+
LocalRef.call(path: referenced_path, doc: @doc)
|
133
|
+
end
|
130
134
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
+
def dereference_file(referenced_path)
|
136
|
+
referenced_path = File.expand_path(referenced_path, doc_dir) unless File.absolute_path?(referenced_path)
|
137
|
+
@file_cache[referenced_path] ||= load_referenced_file(referenced_path)
|
138
|
+
end
|
135
139
|
|
136
|
-
|
137
|
-
|
140
|
+
def load_referenced_file(absolute_path)
|
141
|
+
directory = File.dirname(absolute_path)
|
138
142
|
|
139
|
-
|
140
|
-
|
143
|
+
unless File.exist?(absolute_path)
|
144
|
+
raise FileNotFoundError,
|
145
|
+
"Problem while loading file referenced in #{@filename}: File not found #{absolute_path}"
|
146
|
+
end
|
147
|
+
|
148
|
+
referenced_doc = Loader.handle(absolute_path)
|
149
|
+
Dereferencer.new(@filename, directory, referenced_doc, @file_cache).call
|
150
|
+
end
|
141
151
|
end
|
142
152
|
end
|
143
153
|
end
|
@@ -22,8 +22,10 @@ module OpenapiFirst
|
|
22
22
|
# @attr_reader [String] content_type The content_type of the Rack::Response.
|
23
23
|
def_delegators :@rack_response, :status, :content_type
|
24
24
|
|
25
|
-
# @
|
26
|
-
|
25
|
+
# @return [String] name The name of the operation. Used for generating error messages.
|
26
|
+
def name
|
27
|
+
"#{@operation.name} response status: #{status}"
|
28
|
+
end
|
27
29
|
|
28
30
|
# Checks if the response is valid. Runs the validation unless it has been run before.
|
29
31
|
# @return [Boolean]
|
data/lib/openapi_first/schema.rb
CHANGED
@@ -42,7 +42,7 @@ module OpenapiFirst
|
|
42
42
|
def binary_format(data, property, property_schema, _parent)
|
43
43
|
return unless property_schema.is_a?(Hash) && property_schema['format'] == 'binary'
|
44
44
|
|
45
|
-
data[property] = data
|
45
|
+
data[property] = data.dig(property, :tempfile)&.read if data[property]
|
46
46
|
end
|
47
47
|
end
|
48
48
|
end
|
data/lib/openapi_first.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: openapi_first
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.3.
|
4
|
+
version: 1.3.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andreas Haller
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-03-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: hana
|
@@ -52,20 +52,6 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '1.15'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: mustermann
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - "~>"
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: 3.0.0
|
62
|
-
type: :runtime
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - "~>"
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: 3.0.0
|
69
55
|
- !ruby/object:Gem::Dependency
|
70
56
|
name: openapi_parameters
|
71
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -122,6 +108,7 @@ files:
|
|
122
108
|
- lib/openapi_first/definition.rb
|
123
109
|
- lib/openapi_first/definition/operation.rb
|
124
110
|
- lib/openapi_first/definition/path_item.rb
|
111
|
+
- lib/openapi_first/definition/path_template.rb
|
125
112
|
- lib/openapi_first/definition/request_body.rb
|
126
113
|
- lib/openapi_first/definition/response.rb
|
127
114
|
- lib/openapi_first/definition/responses.rb
|