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 +4 -4
- data/lib/openapi_ruby/adapters/rspec.rb +14 -2
- data/lib/openapi_ruby/components/loader.rb +49 -14
- data/lib/openapi_ruby/components/registry.rb +12 -1
- data/lib/openapi_ruby/core/document_builder.rb +3 -3
- data/lib/openapi_ruby/dsl/operation_context.rb +7 -1
- data/lib/openapi_ruby/version.rb +1 -1
- data/lib/tasks/openapi_ruby.rake +43 -4
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e5b5aa8b42fe199142eb84aa503b7ed590e2cb0399790a3a749f1df5a98c543b
|
|
4
|
+
data.tar.gz: 9a32a812806610a71af36ea018ca7774dbc8cfd8f0ea3d44a73b91303e3dbec2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
#
|
|
144
|
-
|
|
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
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
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
|
|
52
|
+
# Add SchemaValidationError response component
|
|
53
53
|
@components["responses"] ||= {}
|
|
54
|
-
@components["responses"]["
|
|
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/
|
|
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
|
-
|
|
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
|
|
data/lib/openapi_ruby/version.rb
CHANGED
data/lib/tasks/openapi_ruby.rake
CHANGED
|
@@ -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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|