openapi-ruby 2.4.0 → 2.5.1

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: 625d042fe294eeec35636e245418a38ab1cdefb13412fb1cfd355429a90ead83
4
- data.tar.gz: 354e09c385f606421e191f05bccfef6a1cefac42be17485f3eeb4bf38ccf3285
3
+ metadata.gz: e5b5aa8b42fe199142eb84aa503b7ed590e2cb0399790a3a749f1df5a98c543b
4
+ data.tar.gz: 9a32a812806610a71af36ea018ca7774dbc8cfd8f0ea3d44a73b91303e3dbec2
5
5
  SHA512:
6
- metadata.gz: de6d641c194b48f03e20c877a9c9ab787eaf20dd88fe8bf952bb2c22963e98b060f31aea0d5d57321a99d6ffd91fe9b80805b3bbeb4274d17f085b7481747588
7
- data.tar.gz: f9790faa37a74d230addcf0fdaaa346454481c63791e73efa138aa1bdc212cf08111dee1eb1cfcc6b1b867d90189cf4e76bea4305d27474f7098c7214cbebe00
6
+ metadata.gz: 78d10fcbf342b18f50988bf9984767cc911fc8e3466a6f07cc5b6f404277b5fa7cb93bbc41767e1eb185fd46c04503e0fb714c02ebbfa5bc78ae1f1a5393bff3
7
+ data.tar.gz: 6c520011e5868a4c1dca5d6b2bd51ef066cc40c9006cd8411962b250364d4aabcc7ae201982324088611eb4e35055800c90500949ca5dbe4e58df0fcde300333
@@ -140,8 +140,9 @@ module OpenapiRuby
140
140
  end
141
141
 
142
142
  method = operation&.verb || "get"
143
- # Default to JSON Accept header for API requests
144
- headers["Accept"] ||= "application/json"
143
+ # Accept header: use let(:Accept) if defined, otherwise default to JSON
144
+ accept = resolve_let(:Accept)
145
+ headers["Accept"] = accept || "application/json"
145
146
 
146
147
  if body
147
148
  content_type = operation&.request_body_definition&.dig("content")&.keys&.first || "application/json"
@@ -281,6 +282,17 @@ module OpenapiRuby
281
282
  rescue => e
282
283
  warn "[openapi_ruby] Schema generation failed: #{e.message}"
283
284
  end
285
+
286
+ # RSpec's --dry-run mode does not fire after(:suite) hooks.
287
+ # Describe/context blocks are still evaluated (registering DSL contexts),
288
+ # but the hook that writes schemas never runs. Use at_exit as a fallback.
289
+ if config.dry_run?
290
+ at_exit do
291
+ OpenapiRuby::Generator::SchemaWriter.generate_all!
292
+ rescue => e
293
+ warn "[openapi_ruby] Schema generation failed: #{e.message}"
294
+ end
295
+ end
284
296
  end
285
297
  end
286
298
  end
@@ -110,28 +110,63 @@ module OpenapiRuby
110
110
  end
111
111
 
112
112
  def load_with_scope_inference(all_files, scope_paths)
113
+ # Build a map of file path → inferred scope before loading.
114
+ file_scope_map = {}
113
115
  all_files.each do |entry|
114
- inferred_scope = infer_scope(entry[:relative], scope_paths)
116
+ scope = infer_scope(entry[:relative], scope_paths)
117
+ file_scope_map[entry[:file]] = scope if scope
118
+ end
115
119
 
120
+ # Load files, tracking which classes each file registers.
121
+ # We track both newly loaded AND already-loaded classes via before/after diffs.
122
+ class_to_file = {}
123
+ all_files.each do |entry|
116
124
  registered_before = Registry.instance.all_registered_classes.dup
117
125
  require entry[:file]
118
- registered_after = Registry.instance.all_registered_classes
119
-
120
- new_classes = registered_after - registered_before
121
- new_classes.each do |klass|
122
- next if klass._component_scopes_explicitly_set
123
-
124
- if inferred_scope == :shared
125
- klass._component_scopes = []
126
- elsif inferred_scope
127
- Registry.instance.unregister(klass)
128
- klass._component_scopes = [inferred_scope]
129
- Registry.instance.register(klass)
130
- end
126
+ new_classes = Registry.instance.all_registered_classes - registered_before
127
+ new_classes.each { |klass| class_to_file[klass] = entry[:file] }
128
+ end
129
+
130
+ # For components that were already autoloaded by Rails (require returned false,
131
+ # so they didn't appear in the before/after diff), try to match them to files
132
+ # by their class name → file path convention.
133
+ Registry.instance.all_registered_classes.each do |klass|
134
+ next if class_to_file.key?(klass)
135
+
136
+ source_file = find_source_file_for(klass)
137
+ class_to_file[klass] = source_file if source_file
138
+ end
139
+
140
+ # Assign scopes to all registered components based on their source file.
141
+ class_to_file.each do |klass, file|
142
+ next if klass._component_scopes_explicitly_set
143
+
144
+ inferred_scope = file_scope_map[file]
145
+ next unless inferred_scope
146
+
147
+ if inferred_scope == :shared
148
+ klass._component_scopes = []
149
+ else
150
+ Registry.instance.unregister(klass)
151
+ klass._component_scopes = [inferred_scope]
152
+ Registry.instance.register(klass)
131
153
  end
132
154
  end
133
155
  end
134
156
 
157
+ def find_source_file_for(klass)
158
+ return nil unless klass.name
159
+
160
+ # Try the conventional path based on the class name (e.g., Internal::V1::Schemas::User → internal/v1/schemas/user.rb)
161
+ relative = klass.name.underscore + ".rb"
162
+ @paths.each do |base_path|
163
+ expanded = File.expand_path(base_path)
164
+ candidate = File.join(expanded, relative)
165
+ return candidate if File.exist?(candidate)
166
+ end
167
+ nil
168
+ end
169
+
135
170
  def infer_scope(relative_path, scope_paths)
136
171
  scope_paths.sort_by { |prefix, _| -prefix.length }.each do |prefix, scope|
137
172
  return scope&.to_sym if relative_path.start_with?("#{prefix}/")
@@ -94,7 +94,18 @@ module OpenapiRuby
94
94
  result[type_key] = {}
95
95
  components.each_value do |klass|
96
96
  next if klass._schema_hidden
97
- next if scope && !klass._component_scopes.empty? && !klass._component_scopes.include?(scope)
97
+ # When filtering by scope:
98
+ # - Components with matching scope: included
99
+ # - Components explicitly marked as shared (empty scopes + explicitly_set): included
100
+ # - Components with non-matching scope: excluded
101
+ # - Components with no scope assigned (empty scopes + NOT explicitly_set): excluded
102
+ if scope
103
+ if klass._component_scopes.empty?
104
+ next unless klass._component_scopes_explicitly_set
105
+ else
106
+ next unless klass._component_scopes.include?(scope)
107
+ end
108
+ end
98
109
 
99
110
  result[type_key][klass.component_name] = klass.to_openapi
100
111
  end
@@ -49,9 +49,9 @@ module OpenapiRuby
49
49
  private
50
50
 
51
51
  def inject_validation_error_responses!
52
- # Add ValidationError response component
52
+ # Add SchemaValidationError response component
53
53
  @components["responses"] ||= {}
54
- @components["responses"]["ValidationError"] ||= {
54
+ @components["responses"]["SchemaValidationError"] ||= {
55
55
  "description" => "Request validation failed",
56
56
  "content" => {
57
57
  "application/json" => {
@@ -66,7 +66,7 @@ module OpenapiRuby
66
66
  next unless operation.is_a?(Hash) && operation.key?("responses")
67
67
  next if key == "parameters"
68
68
 
69
- operation["responses"]["400"] ||= {"$ref" => "#/components/responses/ValidationError"}
69
+ operation["responses"]["400"] ||= {"$ref" => "#/components/responses/SchemaValidationError"}
70
70
  end
71
71
  end
72
72
  end
@@ -77,7 +77,13 @@ module OpenapiRuby
77
77
  ctx = ResponseContext.new(status_code, description, hidden: hidden)
78
78
  ctx.produces(*@produces_list) if @produces_list.any?
79
79
  ctx.instance_eval(&block) if block
80
- @responses[status_code.to_s] = ctx
80
+
81
+ key = status_code.to_s
82
+ # Don't overwrite a visible response with a hidden one — hidden responses
83
+ # are test-only variants that should not remove the endpoint from the schema.
84
+ existing = @responses[key]
85
+ @responses[key] = ctx unless hidden && existing && !existing.hidden
86
+
81
87
  ctx
82
88
  end
83
89
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OpenapiRuby
4
- VERSION = "2.4.0"
4
+ VERSION = "2.5.1"
5
5
  end
@@ -3,9 +3,48 @@
3
3
  namespace :openapi_ruby do
4
4
  desc "Generate OpenAPI schema files from spec definitions and components"
5
5
  task generate: :environment do
6
- pattern = ENV.fetch("PATTERN", "spec/**/*_spec.rb")
7
- command = "bundle exec rspec --pattern '#{pattern}' --dry-run --order defined"
8
- puts "Generating OpenAPI schemas..."
9
- system(command) || abort("Schema generation failed")
6
+ framework = ENV.fetch("FRAMEWORK", detect_test_framework).to_s
7
+
8
+ case framework
9
+ when "rspec"
10
+ generate_with_rspec
11
+ when "minitest"
12
+ generate_with_minitest
13
+ else
14
+ abort "Unknown test framework '#{framework}'. Set FRAMEWORK=rspec or FRAMEWORK=minitest."
15
+ end
16
+ end
17
+ end
18
+
19
+ def detect_test_framework
20
+ if File.exist?("spec/spec_helper.rb") || File.exist?("spec/rails_helper.rb")
21
+ "rspec"
22
+ elsif File.exist?("test/test_helper.rb")
23
+ "minitest"
24
+ else
25
+ abort "Could not detect test framework. Set FRAMEWORK=rspec or FRAMEWORK=minitest."
10
26
  end
11
27
  end
28
+
29
+ def generate_with_rspec
30
+ pattern = ENV.fetch("PATTERN", "spec/**/*_spec.rb")
31
+ command = "bundle exec rspec --pattern '#{pattern}' --dry-run --order defined"
32
+ puts "Generating OpenAPI schemas (RSpec)..."
33
+ system(command) || abort("Schema generation failed")
34
+ end
35
+
36
+ def generate_with_minitest
37
+ pattern = ENV.fetch("PATTERN", "test/**/*_test.rb")
38
+ puts "Generating OpenAPI schemas (Minitest)..."
39
+
40
+ # Load Rails environment and minitest adapter
41
+ require "openapi_ruby/minitest"
42
+
43
+ # Load all test files to trigger api_path registrations.
44
+ # Minitest's api_path registers DSL contexts at class load time,
45
+ # so simply requiring the files is enough.
46
+ Dir.glob(pattern).each { |f| require File.expand_path(f) }
47
+
48
+ # Generate schemas from the registered contexts
49
+ OpenapiRuby::Generator::SchemaWriter.generate_all!
50
+ 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: 2.4.0
4
+ version: 2.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Morten Hartvig