openapi-ruby 4.0.2 → 4.1.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 +69 -2
- data/lib/openapi_ruby/adapters/context_resolution.rb +113 -0
- data/lib/openapi_ruby/adapters/minitest.rb +22 -14
- data/lib/openapi_ruby/adapters/rspec.rb +15 -15
- data/lib/openapi_ruby/configuration.rb +7 -0
- data/lib/openapi_ruby/errors.rb +7 -0
- data/lib/openapi_ruby/generator/rake_task_support.rb +5 -2
- data/lib/openapi_ruby/generator/test_schema_suppressor.rb +55 -0
- data/lib/openapi_ruby/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7990b0f6b4af407b5df408029221d095428b91e0f0cc71f798f77e6d37901eb5
|
|
4
|
+
data.tar.gz: 4514fb71405d81e25a2e079e9e0ebcb03110f1430c327be386fd4d63c336eff1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6f2f27ba8f415683fb454b707e90eec7b9bb3f6adf5c5c1835b1f4002d83afb898414d4a735cc372d9b6a5a47f636c7aa2d0bf24dee092c5fe2b40180828a218
|
|
7
|
+
data.tar.gz: 666bb48fd0aaa60c3b25ab242e180735513416db9b34aaa22ec9dcabd54cc0dbcb2aeb5a50d36c7ae860f68fda83cb5261e0706561decce219f657fde4003853
|
data/README.md
CHANGED
|
@@ -473,9 +473,17 @@ Loading a test file normally *is* enough to run it: `rails/test_help` requires `
|
|
|
473
473
|
|
|
474
474
|
Schemas are **only** written by the rake task — running tests (`bundle exec rspec`, `rails test`) does not generate or overwrite schema files. This prevents partial schema overwrites when running a subset of specs.
|
|
475
475
|
|
|
476
|
+
### No database required
|
|
477
|
+
|
|
478
|
+
The document is built from your declarations, never from the database — but `rails/test_help` verifies the test schema at require time (`maintain_test_schema!`), and many hand-written helpers add `ActiveRecord::Migration.check_all_pending!`. Both open a connection, which would make a database a hard requirement for generating a document that doesn't need one.
|
|
479
|
+
|
|
480
|
+
Generation stubs both out, so `rake openapi_ruby:generate` runs with no database available. Nothing else about your helper changes, and the stubs exist only inside the generation subprocess — normal test runs still verify the schema as usual.
|
|
481
|
+
|
|
482
|
+
Only the schema *check* is skipped. A connection is still available if your declarations genuinely need one (an enum built from a query at load time, say); such a suite needs a database either way.
|
|
483
|
+
|
|
476
484
|
### Making generation cheaper (optional)
|
|
477
485
|
|
|
478
|
-
Generation only needs your `path` / `api_path` declarations to register.
|
|
486
|
+
Generation only needs your `path` / `api_path` declarations to register. Booting the full test framework and loading fixtures is dead weight, and on a large suite it dominates the runtime.
|
|
479
487
|
|
|
480
488
|
Guard that setup with `OpenapiRuby.schema_generating?`, which returns `true` only in the rake task's subprocess (it sets `OPENAPI_RUBY_GENERATING=true`):
|
|
481
489
|
|
|
@@ -489,7 +497,7 @@ require "rails/test_help"
|
|
|
489
497
|
# ...other test-time setup...
|
|
490
498
|
```
|
|
491
499
|
|
|
492
|
-
This is an optimization
|
|
500
|
+
This is purely an optimization — generation is already correct and database-free without it.
|
|
493
501
|
|
|
494
502
|
One caveat if you do guard: skipping `rails/test_help` also means `fixtures` is undefined, so any test file calling `fixtures :all` in its class body fails to *load*. Point `PATTERN` at just the files carrying `api_path` declarations:
|
|
495
503
|
|
|
@@ -499,6 +507,65 @@ PATTERN="test/integration/api/**/*_test.rb" rake openapi_ruby:generate
|
|
|
499
507
|
|
|
500
508
|
Suites using FactoryBot rather than fixtures don't hit this.
|
|
501
509
|
|
|
510
|
+
### How a request finds its api_path (Style 2)
|
|
511
|
+
|
|
512
|
+
Style 2 separates the `api_path` declaration from the request that exercises
|
|
513
|
+
it, so `assert_api_response` has to match the request back to a declaration. It
|
|
514
|
+
narrows the declared paths by, in order:
|
|
515
|
+
|
|
516
|
+
1. the verb — only paths declaring it stay in
|
|
517
|
+
2. the path params — a path needing `{project_id}` is out if none was supplied,
|
|
518
|
+
and a path is out if it doesn't use every key given in `path_params:`
|
|
519
|
+
3. the expected status — `assert_api_response :put, 422` skips paths that don't
|
|
520
|
+
declare a 422 for that verb
|
|
521
|
+
4. how many supplied keys the path can explain, as either one of its own path
|
|
522
|
+
params or a parameter declared on the operation
|
|
523
|
+
|
|
524
|
+
That resolves a collection path against a member path, nested resources, and
|
|
525
|
+
sibling paths distinguished by status. It cannot resolve paths that agree on all
|
|
526
|
+
four:
|
|
527
|
+
|
|
528
|
+
```ruby
|
|
529
|
+
api_path "/timers/{id}" { put("Update") { response(200, "ok") } }
|
|
530
|
+
api_path "/timers/{id}/start" { put("Start") { response(200, "ok") } }
|
|
531
|
+
api_path "/timers/{id}/stop" { put("Stop") { response(200, "ok") } }
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
Nothing at the call site tells those apart, so that raises
|
|
535
|
+
`OpenapiRuby::AmbiguousApiPath` naming the candidates rather than silently
|
|
536
|
+
picking the first and validating against the wrong response schema. Two ways to
|
|
537
|
+
resolve it. Name the path on the request:
|
|
538
|
+
|
|
539
|
+
```ruby
|
|
540
|
+
assert_api_response :put, 200, path_params: {id: timer.id}, api_path: "/timers/{id}/start"
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
Or, in RSpec, declare each path in its own example group — a nested `describe`
|
|
544
|
+
only sees paths declared at or above it:
|
|
545
|
+
|
|
546
|
+
```ruby
|
|
547
|
+
describe "start" do
|
|
548
|
+
api_path("/timers/{id}/start") { put("Start") { response(200, "ok") } }
|
|
549
|
+
|
|
550
|
+
it { assert_api_response :put, 200, path_params: {id: timer.id} }
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
describe "stop" do
|
|
554
|
+
api_path("/timers/{id}/stop") { put("Stop") { response(200, "ok") } }
|
|
555
|
+
|
|
556
|
+
it { assert_api_response :put, 200, path_params: {id: timer.id} }
|
|
557
|
+
end
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
To require one path per test class regardless, switch on:
|
|
561
|
+
|
|
562
|
+
```ruby
|
|
563
|
+
config.single_api_path_per_class = true
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
`api_path` then raises `OpenapiRuby::MultipleApiPaths` as soon as a class
|
|
567
|
+
declares a second path. Off by default.
|
|
568
|
+
|
|
502
569
|
### Migrating from RSpec to Minitest (or vice versa)
|
|
503
570
|
|
|
504
571
|
When both `spec/spec_helper.rb` and `test/test_helper.rb` are present, the rake task auto-selects `FRAMEWORK=hybrid` — it requires both adapters and loads both glob patterns (`spec/**/*_spec.rb,test/**/*_test.rb`) into one process. Style 1 `path(...)` and Style 2 `api_path(...)` definitions register into the same `MetadataStore`, so a single schema file holds paths contributed by either DSL.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenapiRuby
|
|
4
|
+
module Adapters
|
|
5
|
+
# Shared by the Minitest and RSpec Style 2 adapters.
|
|
6
|
+
#
|
|
7
|
+
# Style 2 separates the `api_path` declaration from the request that
|
|
8
|
+
# exercises it, so the request has to be matched back to a declaration.
|
|
9
|
+
# Everything used to do that here is a hard fact about the request, never a
|
|
10
|
+
# guess: the verb, which path params the template needs against which the
|
|
11
|
+
# caller supplied, whether the candidate declares the status the assertion
|
|
12
|
+
# demands, and whether the remaining params are declared on the operation.
|
|
13
|
+
#
|
|
14
|
+
# That leaves one case it cannot decide. `/timers/{id}` and
|
|
15
|
+
# `/timers/{id}/start` under the same verb, the same status and the same
|
|
16
|
+
# `{id}` are indistinguishable from the call site — the information simply
|
|
17
|
+
# isn't there. Picking one silently sends the request to the wrong endpoint
|
|
18
|
+
# and validates it against the wrong response schema, so a test passes while
|
|
19
|
+
# exercising something else. Raise instead, and name the candidates; the
|
|
20
|
+
# author resolves it by passing `api_path:` or by scoping the declarations.
|
|
21
|
+
module ContextResolution
|
|
22
|
+
module_function
|
|
23
|
+
|
|
24
|
+
def resolve(contexts, method, path_params, owner:, params: {}, expected_status: nil, api_path: nil)
|
|
25
|
+
if api_path
|
|
26
|
+
selected = find_declared(contexts, api_path)
|
|
27
|
+
raise OpenapiRuby::Error, unknown_path_message(contexts, api_path, owner) unless selected
|
|
28
|
+
return selected
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
candidates = contexts.select { |ctx| ctx.operations.key?(method.to_s) }
|
|
32
|
+
return candidates.first if candidates.size <= 1
|
|
33
|
+
|
|
34
|
+
supplied = keys_of(params) | keys_of(path_params)
|
|
35
|
+
required = keys_of(path_params)
|
|
36
|
+
|
|
37
|
+
candidates = narrow(candidates) { |ctx| path_params_fit?(ctx, required, supplied) }
|
|
38
|
+
if expected_status
|
|
39
|
+
candidates = narrow(candidates) { |ctx| declares_status?(ctx, method, expected_status) }
|
|
40
|
+
end
|
|
41
|
+
candidates = fewest_unaccounted(candidates, method, supplied)
|
|
42
|
+
|
|
43
|
+
return candidates.first if candidates.size == 1
|
|
44
|
+
|
|
45
|
+
raise OpenapiRuby::AmbiguousApiPath, ambiguity_message(candidates, method, owner)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# A template only fits if it needs no path param the caller did not supply,
|
|
49
|
+
# and uses every param the caller explicitly declared as one.
|
|
50
|
+
def path_params_fit?(context, required, supplied)
|
|
51
|
+
template = template_params(context)
|
|
52
|
+
|
|
53
|
+
(required - template).empty? && (template - supplied).empty?
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def declares_status?(context, method, expected_status)
|
|
57
|
+
context.operations[method.to_s].responses.key?(expected_status.to_s)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Prefer the candidate that can explain the most supplied keys as either a
|
|
61
|
+
# path param of its own template or a parameter declared on it. A key that
|
|
62
|
+
# fits nowhere means the request was probably meant for a sibling path.
|
|
63
|
+
def fewest_unaccounted(candidates, method, supplied)
|
|
64
|
+
ranked = candidates.group_by { |ctx| (supplied - accounted_keys(ctx, method)).size }
|
|
65
|
+
|
|
66
|
+
ranked[ranked.keys.min]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def accounted_keys(context, method)
|
|
70
|
+
declared = context.path_parameters + (context.operations[method.to_s]&.parameters || [])
|
|
71
|
+
|
|
72
|
+
template_params(context) | declared.filter_map { |param| param["name"]&.to_s }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def template_params(context)
|
|
76
|
+
context.path_template.scan(/\{(\w+)\}/).flatten
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def find_declared(contexts, api_path)
|
|
80
|
+
template = api_path.respond_to?(:path_template) ? api_path.path_template : api_path.to_s
|
|
81
|
+
|
|
82
|
+
contexts.find { |ctx| ctx.path_template == template }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def narrow(candidates)
|
|
86
|
+
narrowed = candidates.select { |ctx| yield(ctx) }
|
|
87
|
+
|
|
88
|
+
narrowed.empty? ? candidates : narrowed
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def keys_of(params)
|
|
92
|
+
params.keys.map(&:to_s)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def ambiguity_message(matches, method, owner)
|
|
96
|
+
paths = matches.map { |ctx| ctx.path_template.inspect }.join(", ")
|
|
97
|
+
|
|
98
|
+
"#{method.to_s.upcase} matches more than one api_path in #{owner}: #{paths}. " \
|
|
99
|
+
"Requests are matched on the verb, the path params supplied and the declared " \
|
|
100
|
+
"response status, none of which tell these apart. Pass api_path: to pick one, " \
|
|
101
|
+
"or declare each api_path in its own class or nested describe block."
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def unknown_path_message(contexts, api_path, owner)
|
|
105
|
+
template = api_path.respond_to?(:path_template) ? api_path.path_template : api_path.to_s
|
|
106
|
+
declared = contexts.map { |ctx| ctx.path_template.inspect }.join(", ")
|
|
107
|
+
|
|
108
|
+
"No api_path #{template.inspect} declared in #{owner}. " \
|
|
109
|
+
"Declared: #{declared.empty? ? "none" : declared}."
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "openapi_ruby"
|
|
4
|
+
require_relative "context_resolution"
|
|
4
5
|
require "cgi"
|
|
5
6
|
require "uri"
|
|
6
7
|
|
|
@@ -20,16 +21,30 @@ module OpenapiRuby
|
|
|
20
21
|
end
|
|
21
22
|
|
|
22
23
|
def api_path(template, &block)
|
|
24
|
+
guard_single_api_path!(template)
|
|
25
|
+
|
|
23
26
|
context = OpenapiRuby::DSL::Context.new(template, schema_name: _openapi_schema_name)
|
|
24
27
|
context.instance_eval(&block) if block
|
|
25
28
|
self._openapi_contexts = _openapi_contexts + [context]
|
|
26
29
|
OpenapiRuby::DSL::MetadataStore.register(context)
|
|
27
30
|
context
|
|
28
31
|
end
|
|
32
|
+
|
|
33
|
+
private def guard_single_api_path!(template)
|
|
34
|
+
return unless OpenapiRuby.configuration.single_api_path_per_class
|
|
35
|
+
|
|
36
|
+
existing = _openapi_contexts.first
|
|
37
|
+
return if existing.nil?
|
|
38
|
+
|
|
39
|
+
raise OpenapiRuby::MultipleApiPaths,
|
|
40
|
+
"#{name || self} already declares api_path #{existing.path_template.inspect}; " \
|
|
41
|
+
"declare #{template.inspect} in its own class."
|
|
42
|
+
end
|
|
29
43
|
end
|
|
30
44
|
|
|
31
|
-
def assert_api_response(method, expected_status, params: {}, headers: {}, body: nil, path_params: {},
|
|
32
|
-
|
|
45
|
+
def assert_api_response(method, expected_status, params: {}, headers: {}, body: nil, path_params: {},
|
|
46
|
+
api_path: nil, &block)
|
|
47
|
+
context = find_context_for(method, path_params, params, expected_status, api_path)
|
|
33
48
|
raise OpenapiRuby::Error, "No api_path defined for #{method.upcase} in #{self.class}" unless context
|
|
34
49
|
|
|
35
50
|
operation = context.operations[method.to_s]
|
|
@@ -123,18 +138,11 @@ module OpenapiRuby
|
|
|
123
138
|
|
|
124
139
|
private
|
|
125
140
|
|
|
126
|
-
def find_context_for(method, path_params)
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if has_path_params
|
|
133
|
-
ctx.path_template.include?("{")
|
|
134
|
-
else
|
|
135
|
-
!ctx.path_template.include?("{")
|
|
136
|
-
end
|
|
137
|
-
end
|
|
141
|
+
def find_context_for(method, path_params, params, expected_status, api_path)
|
|
142
|
+
OpenapiRuby::Adapters::ContextResolution.resolve(
|
|
143
|
+
self.class._openapi_contexts, method, path_params,
|
|
144
|
+
params: params, expected_status: expected_status, api_path: api_path, owner: self.class
|
|
145
|
+
)
|
|
138
146
|
end
|
|
139
147
|
|
|
140
148
|
def expand_path(template, params)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "openapi_ruby"
|
|
4
|
+
require_relative "context_resolution"
|
|
4
5
|
require "cgi"
|
|
5
6
|
require "uri"
|
|
6
7
|
|
|
@@ -21,8 +22,11 @@ module OpenapiRuby
|
|
|
21
22
|
schema_name = metadata[:openapi_schema_name]
|
|
22
23
|
context = DSL::Context.new(template, schema_name: schema_name)
|
|
23
24
|
context.instance_eval(&block) if block
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
# Replace rather than push: RSpec copies parent metadata into child
|
|
26
|
+
# groups by reference, so mutating the array in place would leak this
|
|
27
|
+
# declaration back up to the parent and sideways to its siblings.
|
|
28
|
+
# Building a new one is what makes a nested describe an actual scope.
|
|
29
|
+
metadata[:openapi_api_contexts] = (metadata[:openapi_api_contexts] || []) + [context]
|
|
26
30
|
DSL::MetadataStore.register(context)
|
|
27
31
|
context
|
|
28
32
|
end
|
|
@@ -123,9 +127,10 @@ module OpenapiRuby
|
|
|
123
127
|
# Minitest-style assertion: looks up the api_path context, makes the
|
|
124
128
|
# request, validates the response status + body, then yields to the
|
|
125
129
|
# block for additional expectations.
|
|
126
|
-
def assert_api_response(method, expected_status, params: {}, headers: {}, body: nil, path_params: {},
|
|
130
|
+
def assert_api_response(method, expected_status, params: {}, headers: {}, body: nil, path_params: {},
|
|
131
|
+
api_path: nil, &block)
|
|
127
132
|
meta = ::RSpec.current_example.metadata
|
|
128
|
-
context = find_api_context_for(meta, method, path_params)
|
|
133
|
+
context = find_api_context_for(meta, method, path_params, params, expected_status, api_path)
|
|
129
134
|
raise OpenapiRuby::Error, "No api_path defined for #{method.upcase} in this example group" unless context
|
|
130
135
|
|
|
131
136
|
operation = context.operations[method.to_s]
|
|
@@ -334,19 +339,14 @@ module OpenapiRuby
|
|
|
334
339
|
|
|
335
340
|
private
|
|
336
341
|
|
|
337
|
-
def find_api_context_for(metadata, method, path_params)
|
|
342
|
+
def find_api_context_for(metadata, method, path_params, params, expected_status, api_path)
|
|
338
343
|
contexts = find_in_metadata(metadata, :openapi_api_contexts) || []
|
|
339
|
-
has_path_params = path_params.any?
|
|
340
344
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
else
|
|
347
|
-
!ctx.path_template.include?("{")
|
|
348
|
-
end
|
|
349
|
-
end
|
|
345
|
+
OpenapiRuby::Adapters::ContextResolution.resolve(
|
|
346
|
+
contexts, method, path_params,
|
|
347
|
+
params: params, expected_status: expected_status, api_path: api_path,
|
|
348
|
+
owner: metadata[:full_description] || "this example group"
|
|
349
|
+
)
|
|
350
350
|
end
|
|
351
351
|
|
|
352
352
|
def expand_path(template, params)
|
|
@@ -18,6 +18,12 @@ module OpenapiRuby
|
|
|
18
18
|
# Middleware (runtime validation)
|
|
19
19
|
attr_accessor :request_validation, :response_validation, :coerce_params
|
|
20
20
|
|
|
21
|
+
# Style 2 only: require each test class to declare a single api_path.
|
|
22
|
+
# Off by default — resolution handles more than one path per class, so this
|
|
23
|
+
# is for suites that want the convention enforced anyway.
|
|
24
|
+
# See Adapters::ContextResolution for how a request is matched.
|
|
25
|
+
attr_accessor :single_api_path_per_class
|
|
26
|
+
|
|
21
27
|
# Test DSL: validate that requests match the declared operation before sending.
|
|
22
28
|
# Enabled by default; set to false to disable.
|
|
23
29
|
attr_accessor :test_request_validation
|
|
@@ -51,6 +57,7 @@ module OpenapiRuby
|
|
|
51
57
|
@response_validation = :disabled
|
|
52
58
|
@coerce_params = true
|
|
53
59
|
@test_request_validation = true
|
|
60
|
+
@single_api_path_per_class = false
|
|
54
61
|
@schema_output_dir = "openapi"
|
|
55
62
|
@schema_output_format = :yaml
|
|
56
63
|
@ui_enabled = false
|
data/lib/openapi_ruby/errors.rb
CHANGED
|
@@ -14,6 +14,13 @@ module OpenapiRuby
|
|
|
14
14
|
end
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
+
# Raised when a Style 2 request matches more than one declared api_path.
|
|
18
|
+
class AmbiguousApiPath < Error; end
|
|
19
|
+
|
|
20
|
+
# Raised when a class declares a second api_path and
|
|
21
|
+
# `config.single_api_path_per_class` is on.
|
|
22
|
+
class MultipleApiPaths < Error; end
|
|
23
|
+
|
|
17
24
|
class SchemaValidationError < Error
|
|
18
25
|
attr_reader :validation_errors
|
|
19
26
|
|
|
@@ -42,12 +42,15 @@ module OpenapiRuby
|
|
|
42
42
|
end
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
-
# Prepended to every generated script
|
|
46
|
-
# must
|
|
45
|
+
# Prepended to every generated script, before any consumer file is
|
|
46
|
+
# required. Loading the consumer's test files must neither run them
|
|
47
|
+
# (AutorunSuppressor) nor drag in a database (TestSchemaSuppressor).
|
|
47
48
|
def suppress_autorun
|
|
48
49
|
<<~RUBY.chomp
|
|
49
50
|
require "openapi_ruby/generator/autorun_suppressor"
|
|
51
|
+
require "openapi_ruby/generator/test_schema_suppressor"
|
|
50
52
|
OpenapiRuby::Generator::AutorunSuppressor.install!
|
|
53
|
+
OpenapiRuby::Generator::TestSchemaSuppressor.install!
|
|
51
54
|
RUBY
|
|
52
55
|
end
|
|
53
56
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenapiRuby
|
|
4
|
+
module Generator
|
|
5
|
+
# Schema generation never runs tests, so keeping the test database's schema
|
|
6
|
+
# current is wasted work — but `rails/test_help` does it unconditionally at
|
|
7
|
+
# require time (`rails/testing/maintain_test_schema` → `maintain_test_schema!`),
|
|
8
|
+
# which opens a database connection. That makes a database a hard
|
|
9
|
+
# requirement for generating a document that doesn't depend on one.
|
|
10
|
+
#
|
|
11
|
+
# `maintain_test_schema!` is a no-op when the setting is off, so turning it
|
|
12
|
+
# off for the generation subprocess drops the requirement.
|
|
13
|
+
#
|
|
14
|
+
# This only skips the schema *check*. A connection is still available if the
|
|
15
|
+
# consumer's declarations genuinely need one (say, an enum built from a
|
|
16
|
+
# query at load time) — such a suite needs a database either way.
|
|
17
|
+
module TestSchemaSuppressor
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
def install!
|
|
21
|
+
return unless defined?(::ActiveSupport) && ::ActiveSupport.respond_to?(:on_load)
|
|
22
|
+
|
|
23
|
+
# Registered as a load hook so it applies whenever the consumer's helper
|
|
24
|
+
# boots Rails, rather than depending on require order.
|
|
25
|
+
::ActiveSupport.on_load(:active_record) do
|
|
26
|
+
OpenapiRuby::Generator::TestSchemaSuppressor.disable!
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Neutralises the methods rather than clearing
|
|
31
|
+
# `ActiveRecord.maintain_test_schema`. The flag is reapplied from app
|
|
32
|
+
# config by the `active_record.set_configs` initializer, which runs after
|
|
33
|
+
# any load hook we can register from here — so setting it is silently
|
|
34
|
+
# undone before `rails/test_help` reads it.
|
|
35
|
+
def disable!
|
|
36
|
+
::ActiveRecord::Migration.singleton_class.prepend(MigrationSilencer)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
module MigrationSilencer
|
|
40
|
+
# Called by rails/test_help at require time.
|
|
41
|
+
def maintain_test_schema!
|
|
42
|
+
nil
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Not called by Rails during boot, but a common addition to a
|
|
46
|
+
# hand-written test_helper. Same intent as the above: assert the test
|
|
47
|
+
# database matches the migrations. Nothing is loaded from the database
|
|
48
|
+
# to build the document, so there is nothing to verify.
|
|
49
|
+
def check_all_pending!
|
|
50
|
+
nil
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
data/lib/openapi_ruby/version.rb
CHANGED
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.0
|
|
4
|
+
version: 4.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Morten Hartvig
|
|
@@ -88,6 +88,7 @@ files:
|
|
|
88
88
|
- lib/generators/openapi_ruby/install/templates/openapi_helper.rb.tt
|
|
89
89
|
- lib/openapi-ruby.rb
|
|
90
90
|
- lib/openapi_ruby.rb
|
|
91
|
+
- lib/openapi_ruby/adapters/context_resolution.rb
|
|
91
92
|
- lib/openapi_ruby/adapters/minitest.rb
|
|
92
93
|
- lib/openapi_ruby/adapters/rspec.rb
|
|
93
94
|
- lib/openapi_ruby/components/base.rb
|
|
@@ -108,6 +109,7 @@ files:
|
|
|
108
109
|
- lib/openapi_ruby/generator/autorun_suppressor.rb
|
|
109
110
|
- lib/openapi_ruby/generator/rake_task_support.rb
|
|
110
111
|
- lib/openapi_ruby/generator/schema_writer.rb
|
|
112
|
+
- lib/openapi_ruby/generator/test_schema_suppressor.rb
|
|
111
113
|
- lib/openapi_ruby/middleware/coercion.rb
|
|
112
114
|
- lib/openapi_ruby/middleware/error_handler.rb
|
|
113
115
|
- lib/openapi_ruby/middleware/path_matcher.rb
|