openapi-ruby 3.2.0 → 3.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2e97365e4c2b566d59db1f4946a2b0e1f45f8ee9b938f2b5c049ac0ac9f56dcc
4
- data.tar.gz: 431709a674d6a028fd8a14f47200761215a6e4576888009b3b2b637fd5a453b9
3
+ metadata.gz: b58ff6e6c8ccbf7c8c02d01f6a79ea02717cf3c709de9d1c8a8f870bb44a84fb
4
+ data.tar.gz: e18b82cac3cd08bee2fcbee279d45e9b705b62ad0015feb8e3e5408cf4af63a5
5
5
  SHA512:
6
- metadata.gz: 0a670b21d1043c474b2eead1844c47258521df05b247d561a5578de4c8495a6519b575c7b3d2d4fe4fc54c22d5d524e45ee2fab492e99e3366c8eab5bc381ecf
7
- data.tar.gz: e2981bfa965fef6c6db6767d8e36592b8166fa20f675265c9d0147265565250e845093ca4fb3dc3bd3a7d2a2b1c3a7c6cb9d52169a0142bc0be2a90727a50b77
6
+ metadata.gz: d9248a97e6b5f513516f5d97d87ef81f68047ce4db54c62503667d20751bf15f99fb4644cde3369d017b388184e92e65f904aa171e546c189134d8d1dc742eea
7
+ data.tar.gz: 4a627ed1b9bc867c24263627a27f01f22d5fc50bfde5ffe9d93e87484c50e68f61e739162c198c808b979d484c9c1a3b80921c14eedd32a9c7b076a702a90cb9
data/README.md CHANGED
@@ -193,6 +193,31 @@ class Schemas::AdminUser
193
193
  end
194
194
  ```
195
195
 
196
+ ### Class References
197
+
198
+ Instead of writing `$ref` strings manually, you can pass component classes directly anywhere a `$ref` is expected. This gives you typo protection (via `NameError`), IDE navigation, and less boilerplate:
199
+
200
+ ```ruby
201
+ # Instead of:
202
+ schema "$ref" => "#/components/schemas/User"
203
+ schema type: :array, items: { "$ref" => "#/components/schemas/User" }
204
+
205
+ # You can write:
206
+ schema Schemas::User
207
+ schema type: :array, items: Schemas::User
208
+ ```
209
+
210
+ This works in `schema`, `request_body`, and anywhere nested inside hash/array definitions. Non-component classes raise `ArgumentError`.
211
+
212
+ You can also use the explicit `.to_ref` method:
213
+
214
+ ```ruby
215
+ Schemas::User.to_ref
216
+ # => { "$ref" => "#/components/schemas/User" }
217
+ ```
218
+
219
+ Both class refs and string `$ref` hashes are fully supported — use whichever you prefer.
220
+
196
221
  ### Strong Params
197
222
 
198
223
  Schema components can derive Rails strong params permit lists:
@@ -252,7 +277,7 @@ RSpec.describe "Users API", type: :openapi do
252
277
  produces "application/json"
253
278
 
254
279
  response 200, "returns all users" do
255
- schema type: :array, items: { "$ref" => "#/components/schemas/User" }
280
+ schema type: :array, items: Schemas::User
256
281
 
257
282
  run_test! do
258
283
  expect(JSON.parse(response.body).length).to be > 0
@@ -265,19 +290,17 @@ RSpec.describe "Users API", type: :openapi do
265
290
  consumes "application/json"
266
291
 
267
292
  request_body required: true, content: {
268
- "application/json" => {
269
- schema: { "$ref" => "#/components/schemas/UserInput" }
270
- }
293
+ "application/json" => { schema: Schemas::UserInput }
271
294
  }
272
295
 
273
296
  response 201, "user created" do
274
- schema "$ref" => "#/components/schemas/User"
297
+ schema Schemas::User
275
298
  let(:request_body) { { name: "Jane", email: "jane@example.com" } }
276
299
  run_test!
277
300
  end
278
301
 
279
302
  response 422, "validation errors" do
280
- schema "$ref" => "#/components/schemas/ValidationErrors"
303
+ schema Schemas::ValidationErrors
281
304
  let(:request_body) { { name: "" } }
282
305
  run_test!
283
306
  end
@@ -289,7 +312,7 @@ RSpec.describe "Users API", type: :openapi do
289
312
 
290
313
  get "Get a user" do
291
314
  response 200, "user found" do
292
- schema "$ref" => "#/components/schemas/User"
315
+ schema Schemas::User
293
316
  let(:id) { User.create!(name: "Jane", email: "jane@example.com").id }
294
317
  run_test!
295
318
  end
@@ -319,7 +342,7 @@ RSpec.describe "Users API", type: :openapi do
319
342
  produces "application/json"
320
343
 
321
344
  response 200, "returns all users" do
322
- schema type: :array, items: { "$ref" => "#/components/schemas/User" }
345
+ schema type: :array, items: Schemas::User
323
346
  end
324
347
  end
325
348
 
@@ -327,17 +350,15 @@ RSpec.describe "Users API", type: :openapi do
327
350
  consumes "application/json"
328
351
 
329
352
  request_body required: true, content: {
330
- "application/json" => {
331
- schema: { "$ref" => "#/components/schemas/UserInput" }
332
- }
353
+ "application/json" => { schema: Schemas::UserInput }
333
354
  }
334
355
 
335
356
  response 201, "user created" do
336
- schema "$ref" => "#/components/schemas/User"
357
+ schema Schemas::User
337
358
  end
338
359
 
339
360
  response 422, "validation errors" do
340
- schema "$ref" => "#/components/schemas/ValidationErrors"
361
+ schema Schemas::ValidationErrors
341
362
  end
342
363
  end
343
364
  end
@@ -407,7 +428,7 @@ class UsersApiTest < ActionDispatch::IntegrationTest
407
428
  produces "application/json"
408
429
 
409
430
  response 200, "returns all users" do
410
- schema type: :array, items: { "$ref" => "#/components/schemas/User" }
431
+ schema type: :array, items: Schemas::User
411
432
  end
412
433
  end
413
434
 
@@ -415,13 +436,11 @@ class UsersApiTest < ActionDispatch::IntegrationTest
415
436
  consumes "application/json"
416
437
 
417
438
  request_body required: true, content: {
418
- "application/json" => {
419
- schema: { "$ref" => "#/components/schemas/UserInput" }
420
- }
439
+ "application/json" => { schema: Schemas::UserInput }
421
440
  }
422
441
 
423
442
  response 201, "user created" do
424
- schema "$ref" => "#/components/schemas/User"
443
+ schema Schemas::User
425
444
  end
426
445
  end
427
446
  end
@@ -450,10 +469,34 @@ Generate OpenAPI spec files without running tests:
450
469
  rake openapi_ruby:generate
451
470
  ```
452
471
 
453
- This loads spec/test files to collect API definitions and writes schemas without running any tests. It auto-detects the test framework, or you can set `FRAMEWORK=rspec` or `FRAMEWORK=minitest`. Custom patterns: `PATTERN="packs/*/spec/**/*_spec.rb"`.
472
+ This loads spec/test files to collect API definitions and writes schemas without running any tests. It auto-detects the test framework, or you can set `FRAMEWORK=rspec`, `FRAMEWORK=minitest`, or `FRAMEWORK=hybrid`. Custom patterns: `PATTERN="packs/*/spec/**/*_spec.rb"`.
454
473
 
455
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.
456
475
 
476
+ ### Migrating from RSpec to Minitest (or vice versa)
477
+
478
+ 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.
479
+
480
+ Two things to set up on the consumer side so the two test frameworks don't both wire themselves into Rails' lazy-load hooks during schema generation:
481
+
482
+ ```ruby
483
+ # test/test_helper.rb
484
+ unless ENV["OPENAPI_RUBY_GENERATING"]
485
+ require "rails/test_help"
486
+ # ...other test-time setup...
487
+ end
488
+ ```
489
+
490
+ ```ruby
491
+ # spec/rails_helper.rb
492
+ unless ENV["OPENAPI_RUBY_GENERATING"]
493
+ require "rspec/rails"
494
+ # ...other spec-time setup...
495
+ end
496
+ ```
497
+
498
+ The rake task sets `OPENAPI_RUBY_GENERATING=true` in the subprocess. With the guards in place, neither test framework boots its full Rails integration during generation — only the DSL needs to be live for `api_path` / `path` to register.
499
+
457
500
  ## Runtime Middleware
458
501
 
459
502
  Validate requests and responses against your OpenAPI spec at runtime:
@@ -85,6 +85,10 @@ module OpenapiRuby
85
85
  end
86
86
  end
87
87
 
88
+ def to_ref
89
+ OpenapiRuby::Core::RefResolver.ref_object(_component_type, component_name)
90
+ end
91
+
88
92
  def to_openapi
89
93
  definition = _schema_definition.deep_dup
90
94
 
@@ -147,6 +151,12 @@ module OpenapiRuby
147
151
 
148
152
  def deep_stringify(value)
149
153
  case value
154
+ when Class
155
+ if value < OpenapiRuby::Components::Base
156
+ value.to_ref
157
+ else
158
+ raise ArgumentError, "#{value} is not an OpenapiRuby component"
159
+ end
150
160
  when Hash
151
161
  value.each_with_object({}) { |(k, v), h| h[k.to_s] = deep_stringify(v) }
152
162
  when Array
@@ -45,6 +45,12 @@ module OpenapiRuby
45
45
 
46
46
  def deep_stringify(value)
47
47
  case value
48
+ when Class
49
+ if value < OpenapiRuby::Components::Base
50
+ value.to_ref
51
+ else
52
+ raise ArgumentError, "#{value} is not an OpenapiRuby component"
53
+ end
48
54
  when Hash
49
55
  value.each_with_object({}) { |(k, v), h| h[k.to_s] = deep_stringify(v) }
50
56
  when Array
@@ -128,6 +128,12 @@ module OpenapiRuby
128
128
 
129
129
  def deep_stringify(value)
130
130
  case value
131
+ when Class
132
+ if value < OpenapiRuby::Components::Base
133
+ value.to_ref
134
+ else
135
+ raise ArgumentError, "#{value} is not an OpenapiRuby component"
136
+ end
131
137
  when Hash
132
138
  value.each_with_object({}) { |(k, v), h| h[k.to_s] = deep_stringify(v) }
133
139
  when Array
@@ -72,6 +72,12 @@ module OpenapiRuby
72
72
 
73
73
  def deep_stringify(value)
74
74
  case value
75
+ when Class
76
+ if value < OpenapiRuby::Components::Base
77
+ value.to_ref
78
+ else
79
+ raise ArgumentError, "#{value} is not an OpenapiRuby component"
80
+ end
75
81
  when Hash
76
82
  value.each_with_object({}) { |(k, v), h| h[k.to_s] = deep_stringify(v) }
77
83
  when Array
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenapiRuby
4
+ module Generator
5
+ # Helpers backing the `openapi_ruby:generate` rake task. Extracted
6
+ # so the framework detection / script generation logic is testable
7
+ # without booting Rake.
8
+ module RakeTaskSupport
9
+ module_function
10
+
11
+ def detect_test_framework
12
+ rspec = File.exist?("spec/spec_helper.rb") || File.exist?("spec/rails_helper.rb")
13
+ minitest = File.exist?("test/test_helper.rb")
14
+
15
+ if rspec && minitest
16
+ "hybrid"
17
+ elsif rspec
18
+ "rspec"
19
+ elsif minitest
20
+ "minitest"
21
+ else
22
+ raise ArgumentError,
23
+ "Could not detect test framework. Set FRAMEWORK=rspec, FRAMEWORK=minitest, or FRAMEWORK=hybrid."
24
+ end
25
+ end
26
+
27
+ def default_pattern_for(framework)
28
+ case framework
29
+ when "rspec" then "spec/**/*_spec.rb"
30
+ when "minitest" then "test/**/*_test.rb"
31
+ when "hybrid" then "spec/**/*_spec.rb,test/**/*_test.rb"
32
+ end
33
+ end
34
+
35
+ def generate_script(framework, pattern)
36
+ case framework
37
+ when "rspec" then rspec_script(pattern)
38
+ when "minitest" then minitest_script(pattern)
39
+ when "hybrid" then hybrid_script(pattern)
40
+ else
41
+ raise ArgumentError, "Unknown test framework '#{framework}'."
42
+ end
43
+ end
44
+
45
+ def rspec_script(pattern)
46
+ <<~RUBY
47
+ require "rspec/core"
48
+ $LOAD_PATH.unshift(File.expand_path("spec")) unless $LOAD_PATH.include?(File.expand_path("spec"))
49
+ #{glob_loads(pattern)}
50
+ OpenapiRuby::Generator::SchemaWriter.generate_all!
51
+ RUBY
52
+ end
53
+
54
+ def minitest_script(pattern)
55
+ <<~RUBY
56
+ require "openapi_ruby/minitest"
57
+ #{glob_loads(pattern)}
58
+ OpenapiRuby::Generator::SchemaWriter.generate_all!
59
+ RUBY
60
+ end
61
+
62
+ # Loads both adapters and both file globs in one process. Useful
63
+ # during a phased RSpec → Minitest migration where the suite
64
+ # holds both DSL styles. Consumers should guard
65
+ # `require "rails/test_help"` and `require "rspec/rails"` in
66
+ # their test helpers with `unless ENV["OPENAPI_RUBY_GENERATING"]`
67
+ # so the two test frameworks don't both register Rails lazy
68
+ # hooks in the same process — only the DSL needs to be live for
69
+ # schema generation.
70
+ def hybrid_script(pattern)
71
+ <<~RUBY
72
+ require "rspec/core"
73
+ require "openapi_ruby/rspec"
74
+ require "openapi_ruby/minitest"
75
+ $LOAD_PATH.unshift(File.expand_path("spec")) unless $LOAD_PATH.include?(File.expand_path("spec"))
76
+ $LOAD_PATH.unshift(File.expand_path("test")) unless $LOAD_PATH.include?(File.expand_path("test"))
77
+ #{glob_loads(pattern)}
78
+ OpenapiRuby::Generator::SchemaWriter.generate_all!
79
+ RUBY
80
+ end
81
+
82
+ def glob_loads(pattern)
83
+ pattern.split(",").map do |p|
84
+ %[Dir.glob(#{p.strip.inspect}).sort.each { |f| require File.expand_path(f) }]
85
+ end.join("\n")
86
+ end
87
+ end
88
+ end
89
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OpenapiRuby
4
- VERSION = "3.2.0"
4
+ VERSION = "3.4.0"
5
5
  end
data/lib/openapi_ruby.rb CHANGED
@@ -47,6 +47,7 @@ require_relative "openapi_ruby/testing/request_validator"
47
47
  require_relative "openapi_ruby/testing/assertions"
48
48
  require_relative "openapi_ruby/testing/coverage"
49
49
  require_relative "openapi_ruby/generator/schema_writer"
50
+ require_relative "openapi_ruby/generator/rake_task_support"
50
51
  require_relative "openapi_ruby/middleware/path_matcher"
51
52
  require_relative "openapi_ruby/middleware/coercion"
52
53
  require_relative "openapi_ruby/middleware/error_handler"
@@ -1,55 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "openapi_ruby/generator/rake_task_support"
4
+
3
5
  namespace :openapi_ruby do
4
6
  desc "Generate OpenAPI schema files from spec definitions and components"
5
7
  task :generate do
6
- framework = ENV.fetch("FRAMEWORK", detect_test_framework).to_s
7
- pattern = ENV.fetch("PATTERN", default_pattern_for(framework))
8
+ support = OpenapiRuby::Generator::RakeTaskSupport
9
+ framework = ENV.fetch("FRAMEWORK") { support.detect_test_framework }.to_s
10
+ pattern = ENV.fetch("PATTERN") { support.default_pattern_for(framework) }
8
11
 
9
12
  # Spawn a subprocess so RAILS_ENV defaults to "test" cleanly,
10
13
  # just like rswag did with RSpec::Core::RakeTask.
11
14
  env = {"RAILS_ENV" => ENV.fetch("RAILS_ENV", "test"), "OPENAPI_RUBY_GENERATING" => "true"}
12
- script = generate_script(framework, pattern)
15
+ script = support.generate_script(framework, pattern)
13
16
  command = "bundle exec ruby -e #{Shellwords.escape(script)}"
14
17
 
15
18
  puts "Generating OpenAPI schemas (#{framework})..."
16
19
  system(env, command) || abort("Schema generation failed")
17
20
  end
18
21
  end
19
-
20
- def detect_test_framework
21
- if File.exist?("spec/spec_helper.rb") || File.exist?("spec/rails_helper.rb")
22
- "rspec"
23
- elsif File.exist?("test/test_helper.rb")
24
- "minitest"
25
- else
26
- abort "Could not detect test framework. Set FRAMEWORK=rspec or FRAMEWORK=minitest."
27
- end
28
- end
29
-
30
- def default_pattern_for(framework)
31
- case framework
32
- when "rspec" then "spec/**/*_spec.rb"
33
- when "minitest" then "test/**/*_test.rb"
34
- end
35
- end
36
-
37
- def generate_script(framework, pattern)
38
- case framework
39
- when "rspec"
40
- <<~RUBY
41
- require "rspec/core"
42
- $LOAD_PATH.unshift(File.expand_path("spec")) unless $LOAD_PATH.include?(File.expand_path("spec"))
43
- #{pattern.split(",").map { |p| %[Dir.glob(#{p.strip.inspect}).sort.each { |f| require File.expand_path(f) }] }.join("\n")}
44
- OpenapiRuby::Generator::SchemaWriter.generate_all!
45
- RUBY
46
- when "minitest"
47
- <<~RUBY
48
- require "openapi_ruby/minitest"
49
- #{pattern.split(",").map { |p| %[Dir.glob(#{p.strip.inspect}).sort.each { |f| require File.expand_path(f) }] }.join("\n")}
50
- OpenapiRuby::Generator::SchemaWriter.generate_all!
51
- RUBY
52
- else
53
- abort "Unknown test framework '#{framework}'."
54
- end
55
- end
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: 3.2.0
4
+ version: 3.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Morten Hartvig
@@ -105,6 +105,7 @@ files:
105
105
  - lib/openapi_ruby/dsl/response_context.rb
106
106
  - lib/openapi_ruby/engine.rb
107
107
  - lib/openapi_ruby/errors.rb
108
+ - lib/openapi_ruby/generator/rake_task_support.rb
108
109
  - lib/openapi_ruby/generator/schema_writer.rb
109
110
  - lib/openapi_ruby/middleware/coercion.rb
110
111
  - lib/openapi_ruby/middleware/error_handler.rb