openapi-ruby 4.2.0 → 5.0.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/README.md +34 -1
- data/lib/generators/openapi_ruby/component/component_generator.rb +21 -1
- data/lib/openapi_ruby/adapters/context_resolution.rb +1 -1
- data/lib/openapi_ruby/adapters/minitest.rb +2 -1
- data/lib/openapi_ruby/adapters/rspec.rb +3 -2
- data/lib/openapi_ruby/core/document_builder.rb +1 -0
- data/lib/openapi_ruby/dsl/context.rb +1 -1
- data/lib/openapi_ruby/dsl/operation_context.rb +1 -1
- data/lib/openapi_ruby/parameter_names.rb +89 -0
- data/lib/openapi_ruby/testing/request_builder.rb +1 -1
- data/lib/openapi_ruby/testing/request_validator.rb +15 -4
- data/lib/openapi_ruby/version.rb +1 -1
- data/lib/openapi_ruby.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f937b3be2ac21a6d99687ca9ae2e0a625f89747c597fca146a7f04398d55cedc
|
|
4
|
+
data.tar.gz: e185d294c49caf0f33b1ad4cfc4a0512968f94ecb8f0f8184affcdd69230c94c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7509404bf36980da8c58fde64c22b868e8216aaba20a13849904eedda75245f5015fece24a757e4d7c9c5b40c420554c9538afa30ab591e6b93086cce0c85928
|
|
7
|
+
data.tar.gz: 1a00f2de46c1d363686952541948bc20930bd27397a66381923c1a01262e833eedcc0e3298c3601d0c84ae288ead7a754f73825fca0d292711588823be3469bc
|
data/README.md
CHANGED
|
@@ -182,6 +182,37 @@ class Schemas::User
|
|
|
182
182
|
end
|
|
183
183
|
```
|
|
184
184
|
|
|
185
|
+
#### Parameter names
|
|
186
|
+
|
|
187
|
+
The same setting covers parameter names, so parameters match the component keys instead of contradicting them. Declare them in snake_case — that keeps `let(:page_size)` and `params: {page_size: 20}` idiomatic Ruby — and the camelCased name is what reaches both the document and the request:
|
|
188
|
+
|
|
189
|
+
```ruby
|
|
190
|
+
api_path "/users/{user_id}" do
|
|
191
|
+
parameter name: :user_id, in: :path, schema: { type: :integer }
|
|
192
|
+
|
|
193
|
+
get "List posts" do
|
|
194
|
+
parameter name: :page_size, in: :query, schema: { type: :integer }
|
|
195
|
+
# ...
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
```yaml
|
|
201
|
+
paths:
|
|
202
|
+
/users/{userId}: # template variables travel with the parameter names
|
|
203
|
+
parameters:
|
|
204
|
+
- name: userId
|
|
205
|
+
in: path
|
|
206
|
+
get:
|
|
207
|
+
parameters:
|
|
208
|
+
- name: pageSize
|
|
209
|
+
in: query
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
The test DSL sends what the document promises: `GET /users/7?pageSize=20`. Header parameters keep their declared spelling — HTTP header names are conventionally hyphenated and matched case-insensitively, so camelizing them would rename headers nobody asked to rename.
|
|
213
|
+
|
|
214
|
+
If your API takes snake_case parameters, set `config.camelize_keys = false` (or keep the parameter names camelCased in the declaration, which passes through unchanged either way).
|
|
215
|
+
|
|
185
216
|
### Scopes
|
|
186
217
|
|
|
187
218
|
Assign components to scopes for multiple API specs:
|
|
@@ -253,6 +284,8 @@ rails generate openapi_ruby:component User schemas
|
|
|
253
284
|
rails generate openapi_ruby:component BearerAuth security_schemes
|
|
254
285
|
```
|
|
255
286
|
|
|
287
|
+
Files land in the first entry of `config.component_paths`, so the generator follows whatever the loader reads.
|
|
288
|
+
|
|
256
289
|
## Testing with RSpec
|
|
257
290
|
|
|
258
291
|
```ruby
|
|
@@ -621,7 +654,7 @@ Rails picks up its wiring from the engine. Every other host makes the same calls
|
|
|
621
654
|
| `openapi_ruby:install` / `:component` generators | ✅ | — | — |
|
|
622
655
|
| `openapi_permit` strong params | ✅ | — | — |
|
|
623
656
|
|
|
624
|
-
Versions covered by CI: Rails 7.0–8.0, Hanami 2.3 and 3.0, Sinatra 3.2 and 4.2. Working reference apps live in [`spec/dummy`](spec/dummy), [`spec/hanami_dummy`](spec/hanami_dummy), and [`spec/sinatra_dummy`](spec/sinatra_dummy).
|
|
657
|
+
Versions covered by CI: Ruby 3.2–4.0, Rails 7.0–8.0, Hanami 2.3 and 3.0, Sinatra 3.2 and 4.2. Working reference apps live in [`spec/dummy`](spec/dummy), [`spec/hanami_dummy`](spec/hanami_dummy), and [`spec/sinatra_dummy`](spec/sinatra_dummy).
|
|
625
658
|
|
|
626
659
|
### Hanami
|
|
627
660
|
|
|
@@ -12,11 +12,31 @@ module OpenapiRuby
|
|
|
12
12
|
|
|
13
13
|
def create_component_file
|
|
14
14
|
template "component.rb.tt",
|
|
15
|
-
File.join(
|
|
15
|
+
File.join(component_path, component_type, "#{file_name}.rb")
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
private
|
|
19
19
|
|
|
20
|
+
# Components::Loader only looks under the configured paths, so anything
|
|
21
|
+
# written to a hardcoded app/api_components is invisible on hosts that
|
|
22
|
+
# default elsewhere (Hanami) or configure their own.
|
|
23
|
+
def component_path
|
|
24
|
+
path = OpenapiRuby.configuration.component_paths.first ||
|
|
25
|
+
Configuration.default_component_paths.first
|
|
26
|
+
relativize(path)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# An initializer may hold an absolute Rails.root.join(...) path; Thor
|
|
30
|
+
# reports the destination verbatim, and an absolute one reads as noise.
|
|
31
|
+
def relativize(path)
|
|
32
|
+
pathname = Pathname.new(path)
|
|
33
|
+
return path unless pathname.absolute?
|
|
34
|
+
|
|
35
|
+
pathname.relative_path_from(Pathname.new(destination_root)).to_s
|
|
36
|
+
rescue ArgumentError
|
|
37
|
+
path
|
|
38
|
+
end
|
|
39
|
+
|
|
20
40
|
def class_name
|
|
21
41
|
name.camelize
|
|
22
42
|
end
|
|
@@ -69,7 +69,7 @@ module OpenapiRuby
|
|
|
69
69
|
def accounted_keys(context, method)
|
|
70
70
|
declared = context.path_parameters + (context.operations[method.to_s]&.parameters || [])
|
|
71
71
|
|
|
72
|
-
template_params(context) | declared.
|
|
72
|
+
template_params(context) | declared.flat_map { |param| OpenapiRuby::ParameterNames.lookup_names(param) }
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
def template_params(context)
|
|
@@ -90,6 +90,7 @@ module OpenapiRuby
|
|
|
90
90
|
|
|
91
91
|
# Build query params (exclude path params)
|
|
92
92
|
query_params = params.reject { |k, _| path_param_names(context).include?(k.to_s) }
|
|
93
|
+
query_params = ParameterNames.rename_keys(query_params, operation.parameters)
|
|
93
94
|
|
|
94
95
|
# Execute the request
|
|
95
96
|
if body
|
|
@@ -180,7 +181,7 @@ module OpenapiRuby
|
|
|
180
181
|
end
|
|
181
182
|
|
|
182
183
|
def path_param_names(context)
|
|
183
|
-
context.path_parameters.
|
|
184
|
+
context.path_parameters.flat_map { |p| ParameterNames.lookup_names(p) }
|
|
184
185
|
end
|
|
185
186
|
|
|
186
187
|
def resolve_base_path(schema_name)
|
|
@@ -156,8 +156,9 @@ module OpenapiRuby
|
|
|
156
156
|
|
|
157
157
|
headers["Accept"] ||= "application/json"
|
|
158
158
|
|
|
159
|
-
path_param_names = context.path_parameters.
|
|
159
|
+
path_param_names = context.path_parameters.flat_map { |p| ParameterNames.lookup_names(p) }
|
|
160
160
|
query_params = params.reject { |k, _| path_param_names.include?(k.to_s) }
|
|
161
|
+
query_params = ParameterNames.rename_keys(query_params, operation.parameters)
|
|
161
162
|
|
|
162
163
|
if body
|
|
163
164
|
content_type = operation.request_body_definition&.dig("content")&.keys&.first || "application/json"
|
|
@@ -248,7 +249,7 @@ module OpenapiRuby
|
|
|
248
249
|
next if val.nil?
|
|
249
250
|
|
|
250
251
|
case param["in"]
|
|
251
|
-
when "query" then params[
|
|
252
|
+
when "query" then params[ParameterNames.wire_name(param)] = val
|
|
252
253
|
when "header" then headers[name] = val
|
|
253
254
|
end
|
|
254
255
|
end
|
|
@@ -32,7 +32,7 @@ module OpenapiRuby
|
|
|
32
32
|
def to_openapi
|
|
33
33
|
result = {}
|
|
34
34
|
|
|
35
|
-
result["parameters"] = @path_parameters if @path_parameters.any?
|
|
35
|
+
result["parameters"] = ParameterNames.parameters_for_document(@path_parameters) if @path_parameters.any?
|
|
36
36
|
|
|
37
37
|
@operations.each do |verb, op|
|
|
38
38
|
result[verb] = op.to_openapi
|
|
@@ -93,7 +93,7 @@ module OpenapiRuby
|
|
|
93
93
|
result["summary"] = @summary if @summary
|
|
94
94
|
result["tags"] = @tags_list if @tags_list.any?
|
|
95
95
|
result.merge!(@metadata)
|
|
96
|
-
result["parameters"] = @parameters if @parameters.any?
|
|
96
|
+
result["parameters"] = ParameterNames.parameters_for_document(@parameters) if @parameters.any?
|
|
97
97
|
result["security"] = @security_list if @security_list
|
|
98
98
|
|
|
99
99
|
result["requestBody"] = build_request_body if @request_body_definition
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenapiRuby
|
|
4
|
+
# Parameters are declared in snake_case so `let(:page_size)` and
|
|
5
|
+
# `params: {page_size: 1}` stay idiomatic Ruby. With `camelize_keys` on, the
|
|
6
|
+
# name that reaches the document *and* the wire is camelCased, so parameters
|
|
7
|
+
# match the component keys instead of contradicting them.
|
|
8
|
+
#
|
|
9
|
+
# Header parameters keep their declared spelling: HTTP header names are
|
|
10
|
+
# conventionally hyphenated and matched case-insensitively, so camelizing
|
|
11
|
+
# them would rename headers nobody asked to rename.
|
|
12
|
+
module ParameterNames
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
def camelize?
|
|
16
|
+
OpenapiRuby.configuration.camelize_keys
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def camelize(name)
|
|
20
|
+
return name.to_s unless camelize?
|
|
21
|
+
|
|
22
|
+
Components::KeyTransformer.camelize(name)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# The name a declared parameter carries in the document and on the wire.
|
|
26
|
+
def wire_name(param)
|
|
27
|
+
name = param["name"] || param[:name]
|
|
28
|
+
return nil if name.nil?
|
|
29
|
+
return name.to_s if header?(param)
|
|
30
|
+
|
|
31
|
+
camelize(name)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Both spellings of a declared parameter. Value lookups go through this so a
|
|
35
|
+
# caller that passes the declared name still resolves after the wire name
|
|
36
|
+
# diverged from it.
|
|
37
|
+
def lookup_names(param)
|
|
38
|
+
declared = (param["name"] || param[:name])&.to_s
|
|
39
|
+
return [] unless declared
|
|
40
|
+
|
|
41
|
+
[declared, wire_name(param)].compact.uniq
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def parameters_for_document(parameters)
|
|
45
|
+
return parameters unless camelize?
|
|
46
|
+
|
|
47
|
+
parameters.map do |param|
|
|
48
|
+
wire = wire_name(param)
|
|
49
|
+
next param if wire.nil? || wire == param["name"].to_s
|
|
50
|
+
|
|
51
|
+
param.merge("name" => wire)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# `/users/{user_id}` -> `/users/{userId}`. The template variables have to
|
|
56
|
+
# travel with the parameter names: OpenAPI requires each one to be backed by
|
|
57
|
+
# a path parameter of the same name.
|
|
58
|
+
def in_template(template)
|
|
59
|
+
return template unless camelize?
|
|
60
|
+
|
|
61
|
+
template.gsub(/\{(\w+)\}/) { "{#{camelize(::Regexp.last_match(1))}}" }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Rename the keys of a request value hash from their declared spelling to
|
|
65
|
+
# the wire spelling. Keys that match no declared parameter are left alone —
|
|
66
|
+
# undeclared params are passed through as the caller wrote them.
|
|
67
|
+
def rename_keys(values, parameters)
|
|
68
|
+
return values unless camelize?
|
|
69
|
+
|
|
70
|
+
mapping = parameters.each_with_object({}) do |param, acc|
|
|
71
|
+
declared = (param["name"] || param[:name])&.to_s
|
|
72
|
+
next unless declared
|
|
73
|
+
|
|
74
|
+
wire = wire_name(param)
|
|
75
|
+
acc[declared] = wire if wire && wire != declared
|
|
76
|
+
end
|
|
77
|
+
return values if mapping.empty?
|
|
78
|
+
|
|
79
|
+
values.each_with_object({}) do |(key, value), acc|
|
|
80
|
+
renamed = mapping[key.to_s]
|
|
81
|
+
acc[renamed.nil? ? key : renamed] = value
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def header?(param)
|
|
86
|
+
(param["in"] || param[:in]).to_s == "header"
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -37,7 +37,7 @@ module OpenapiRuby
|
|
|
37
37
|
result = {}
|
|
38
38
|
query_params.each do |param|
|
|
39
39
|
name = param["name"]
|
|
40
|
-
result[
|
|
40
|
+
result[ParameterNames.wire_name(param)] = @param_values[name] if @param_values.key?(name)
|
|
41
41
|
end
|
|
42
42
|
result
|
|
43
43
|
end
|
|
@@ -39,19 +39,30 @@ module OpenapiRuby
|
|
|
39
39
|
|
|
40
40
|
private
|
|
41
41
|
|
|
42
|
+
# A value may arrive under the declared name or the camelized wire name,
|
|
43
|
+
# depending on whether the caller wrote it or an adapter already renamed
|
|
44
|
+
# it for the request. Both spellings resolve.
|
|
42
45
|
def extract_param_value(param, params, headers, path_params)
|
|
43
|
-
|
|
46
|
+
names = ParameterNames.lookup_names(param)
|
|
44
47
|
|
|
45
48
|
case param["in"]
|
|
46
49
|
when "query"
|
|
47
|
-
params
|
|
50
|
+
fetch_any(params, names)
|
|
48
51
|
when "path"
|
|
49
|
-
path_params
|
|
52
|
+
fetch_any(path_params, names)
|
|
50
53
|
when "header"
|
|
51
|
-
headers
|
|
54
|
+
fetch_any(headers, names.flat_map { |name| [name, name.downcase] })
|
|
52
55
|
end
|
|
53
56
|
end
|
|
54
57
|
|
|
58
|
+
def fetch_any(store, names)
|
|
59
|
+
names.each do |name|
|
|
60
|
+
value = store[name.to_sym] || store[name.to_s]
|
|
61
|
+
return value if value
|
|
62
|
+
end
|
|
63
|
+
nil
|
|
64
|
+
end
|
|
65
|
+
|
|
55
66
|
def validate_request_body(operation, body)
|
|
56
67
|
errors = []
|
|
57
68
|
rb_spec = operation.request_body_definition
|
data/lib/openapi_ruby/version.rb
CHANGED
data/lib/openapi_ruby.rb
CHANGED
|
@@ -54,6 +54,7 @@ require_relative "openapi_ruby/components/key_transformer"
|
|
|
54
54
|
require_relative "openapi_ruby/components/registry"
|
|
55
55
|
require_relative "openapi_ruby/components/base"
|
|
56
56
|
require_relative "openapi_ruby/components/loader"
|
|
57
|
+
require_relative "openapi_ruby/parameter_names"
|
|
57
58
|
require_relative "openapi_ruby/dsl/response_context"
|
|
58
59
|
require_relative "openapi_ruby/dsl/operation_context"
|
|
59
60
|
require_relative "openapi_ruby/dsl/context"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: openapi-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 5.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Morten Hartvig
|
|
@@ -106,6 +106,7 @@ files:
|
|
|
106
106
|
- lib/openapi_ruby/middleware/response_validation.rb
|
|
107
107
|
- lib/openapi_ruby/middleware/schema_resolver.rb
|
|
108
108
|
- lib/openapi_ruby/minitest.rb
|
|
109
|
+
- lib/openapi_ruby/parameter_names.rb
|
|
109
110
|
- lib/openapi_ruby/rack_app.rb
|
|
110
111
|
- lib/openapi_ruby/rake_tasks.rb
|
|
111
112
|
- lib/openapi_ruby/rspec.rb
|