openapi-ruby 2.0.0 → 2.2.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 +8 -2
- data/app/controllers/openapi_ruby/ui_controller.rb +31 -6
- data/app/views/openapi_ruby/oauth2_redirect.html +83 -0
- data/config/routes.rb +2 -1
- data/lib/openapi-ruby.rb +3 -0
- data/lib/openapi_ruby/components/base.rb +38 -8
- data/lib/openapi_ruby/components/key_transformer.rb +14 -5
- data/lib/openapi_ruby/components/loader.rb +48 -43
- data/lib/openapi_ruby/components/registry.rb +43 -7
- data/lib/openapi_ruby/engine.rb +2 -15
- data/lib/openapi_ruby/version.rb +1 -1
- metadata +7 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d7c1b8cfa3a8ecc15d1e378b9354d359333b5ac8d43f9cdd1de609d60830a839
|
|
4
|
+
data.tar.gz: c987648c9d766474bb5665d115647513c1ae7d6c3d4c5ea4478ec83d3b7fed22
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 01dc4b64bd6d67a48659d1d1ffb8f77dc93609d3ff790664d60ad312c8b38f2ff986ec013ae52cf14dda243f396637c39366c21985086098bb7c3effa0f4a813
|
|
7
|
+
data.tar.gz: d5df229413a0836644665b8ff29976764c547383ea6443eea20d156d6db8671405f8e84ddec26a25d11b1b3456d84157e44571ebb32ecc58265209e04b20e5b1
|
data/README.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="logo.svg" alt="openapi_ruby" width="200">
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
<h1 align="center">openapi_ruby</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
A unified OpenAPI 3.1 toolkit for Rails that combines test-driven spec generation, reusable schema components as Ruby classes, and runtime request/response validation middleware. Works with both RSpec and Minitest.
|
|
9
|
+
</p>
|
|
4
10
|
|
|
5
11
|
Replaces [rswag](https://github.com/rswag/rswag), [rswag-schema-components](https://github.com/101skills-gmbh/rswag-schema-components), and [committee](https://github.com/interagent/committee) with a single gem.
|
|
6
12
|
|
|
@@ -8,14 +8,19 @@ module OpenapiRuby
|
|
|
8
8
|
return head :not_found unless OpenapiRuby.configuration.ui_enabled
|
|
9
9
|
|
|
10
10
|
config = OpenapiRuby.configuration
|
|
11
|
-
@schemas = config.schemas
|
|
12
|
-
@default_schema = @schemas.first
|
|
11
|
+
@schemas = config.schemas
|
|
13
12
|
@ui_config = config.ui_config
|
|
14
|
-
@schema_url = openapi_ruby.schema_path(@default_schema, format: schema_format)
|
|
15
13
|
|
|
16
14
|
render html: swagger_ui_html.html_safe
|
|
17
15
|
end
|
|
18
16
|
|
|
17
|
+
def oauth2_redirect
|
|
18
|
+
return head :not_found unless OpenapiRuby.configuration.ui_enabled
|
|
19
|
+
|
|
20
|
+
file = File.join(OpenapiRuby::Engine.root, "app", "views", "openapi_ruby", "oauth2_redirect.html")
|
|
21
|
+
render file: file, layout: false, content_type: "text/html"
|
|
22
|
+
end
|
|
23
|
+
|
|
19
24
|
private
|
|
20
25
|
|
|
21
26
|
def schema_format
|
|
@@ -39,16 +44,20 @@ module OpenapiRuby
|
|
|
39
44
|
<body>
|
|
40
45
|
<div id="swagger-ui"></div>
|
|
41
46
|
<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
|
|
47
|
+
<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-standalone-preset.js"></script>
|
|
42
48
|
<script>
|
|
43
49
|
SwaggerUIBundle({
|
|
44
|
-
|
|
50
|
+
#{schema_urls_js},
|
|
45
51
|
dom_id: '#swagger-ui',
|
|
46
52
|
deepLinking: true,
|
|
47
53
|
presets: [
|
|
48
54
|
SwaggerUIBundle.presets.apis,
|
|
49
|
-
|
|
55
|
+
SwaggerUIStandalonePreset
|
|
50
56
|
],
|
|
51
|
-
|
|
57
|
+
plugins: [
|
|
58
|
+
SwaggerUIBundle.plugins.DownloadUrl
|
|
59
|
+
],
|
|
60
|
+
layout: "#{(@schemas.size > 1) ? "StandaloneLayout" : "BaseLayout"}",
|
|
52
61
|
#{ui_config_js}
|
|
53
62
|
});
|
|
54
63
|
</script>
|
|
@@ -57,6 +66,22 @@ module OpenapiRuby
|
|
|
57
66
|
HTML
|
|
58
67
|
end
|
|
59
68
|
|
|
69
|
+
def schema_urls_js
|
|
70
|
+
fmt = schema_format
|
|
71
|
+
if @schemas.size > 1
|
|
72
|
+
urls = @schemas.map { |name, schema_config|
|
|
73
|
+
title = schema_config.dig(:info, :title) || name.to_s
|
|
74
|
+
url = openapi_ruby.schema_path(name.to_s, format: fmt)
|
|
75
|
+
{url: url, name: title}
|
|
76
|
+
}
|
|
77
|
+
"urls: #{urls.to_json}"
|
|
78
|
+
else
|
|
79
|
+
name = @schemas.keys.first.to_s
|
|
80
|
+
url = openapi_ruby.schema_path(name, format: fmt)
|
|
81
|
+
"url: \"#{url}\""
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
60
85
|
def ui_config_js
|
|
61
86
|
@ui_config.except(:title).map { |k, v|
|
|
62
87
|
"#{k}: #{v.to_json}"
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en-US">
|
|
3
|
+
<head>
|
|
4
|
+
<title>Swagger UI: OAuth2 Redirect</title>
|
|
5
|
+
</head>
|
|
6
|
+
<body>
|
|
7
|
+
<script>
|
|
8
|
+
'use strict';
|
|
9
|
+
function run () {
|
|
10
|
+
var oauth2 = window.opener.swaggerUIRedirectOauth2;
|
|
11
|
+
var sentState = oauth2.state;
|
|
12
|
+
var redirectUrl = oauth2.redirectUrl;
|
|
13
|
+
var isValid, qp, arr;
|
|
14
|
+
|
|
15
|
+
if (/code|token|error/.test(window.location.href)) {
|
|
16
|
+
if (window.location.search) {
|
|
17
|
+
qp = parseSearch(window.location.search);
|
|
18
|
+
} else {
|
|
19
|
+
qp = parseHash(window.location.hash);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
isValid = qp.state === sentState;
|
|
23
|
+
|
|
24
|
+
if ((
|
|
25
|
+
oauth2.auth.schema.get("flow") === "accessCode"||
|
|
26
|
+
oauth2.auth.schema.get("flow") === "authorizationCode"||
|
|
27
|
+
oauth2.auth.schema.get("flow") === "authorization_code"
|
|
28
|
+
) && !oauth2.auth.code) {
|
|
29
|
+
if (!isValid) {
|
|
30
|
+
oauth2.errCb({
|
|
31
|
+
authId: oauth2.auth.name,
|
|
32
|
+
source: "auth",
|
|
33
|
+
level: "warning",
|
|
34
|
+
message: "Authorization may be unsafe, passed state was changed in server. The passed state wasn't returned from auth server."
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (qp.code) {
|
|
39
|
+
delete qp.state;
|
|
40
|
+
oauth2.auth.code = qp.code;
|
|
41
|
+
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
|
|
42
|
+
} else {
|
|
43
|
+
var oauthErrorMsg;
|
|
44
|
+
if (qp.error) {
|
|
45
|
+
oauthErrorMsg = "["+qp.error+"]: " + (qp.error_description ? qp.error_description + ". " : "no accessCode received from the server. ") + (qp.error_uri ? "More info: "+qp.error_uri : "");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
oauth2.errCb({
|
|
49
|
+
authId: oauth2.auth.name,
|
|
50
|
+
source: "auth",
|
|
51
|
+
level: "error",
|
|
52
|
+
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server."
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
window.close();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function parseSearch(str) {
|
|
63
|
+
return parseParams(str.substr(1));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function parseHash(str) {
|
|
67
|
+
return parseParams(str.substr(1));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function parseParams(str) {
|
|
71
|
+
return str.split("&").reduce(function (params, param) {
|
|
72
|
+
var keyValue = param.split("=");
|
|
73
|
+
params[keyValue[0]] = decodeURIComponent(keyValue[1]);
|
|
74
|
+
return params;
|
|
75
|
+
}, {});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
window.addEventListener('DOMContentLoaded', function () {
|
|
79
|
+
run();
|
|
80
|
+
});
|
|
81
|
+
</script>
|
|
82
|
+
</body>
|
|
83
|
+
</html>
|
data/config/routes.rb
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
OpenapiRuby::Engine.routes.draw do
|
|
4
4
|
get "schemas", to: "schemas#index", as: :schemas
|
|
5
|
-
get "schemas
|
|
5
|
+
get "schemas/*id", to: "schemas#show", as: :schema
|
|
6
6
|
|
|
7
7
|
get "ui", to: "ui#index", as: :ui
|
|
8
|
+
get "oauth2-redirect.html", to: "ui#oauth2_redirect", as: :oauth2_redirect
|
|
8
9
|
end
|
data/lib/openapi-ruby.rb
ADDED
|
@@ -4,13 +4,20 @@ module OpenapiRuby
|
|
|
4
4
|
module Components
|
|
5
5
|
module Base
|
|
6
6
|
def self.included(base)
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
base.
|
|
10
|
-
|
|
11
|
-
base.
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
# Guard against re-including in subclasses that already inherited Base.
|
|
8
|
+
# Re-running class_attribute with default: would overwrite inherited values.
|
|
9
|
+
already_included = base.respond_to?(:_schema_definition)
|
|
10
|
+
|
|
11
|
+
base.extend ClassMethods unless already_included
|
|
12
|
+
|
|
13
|
+
unless already_included
|
|
14
|
+
base.class_attribute :_schema_definition, default: {}
|
|
15
|
+
base.class_attribute :_schema_hidden, default: false
|
|
16
|
+
base.class_attribute :_skip_key_transformation, default: false
|
|
17
|
+
base.class_attribute :_component_type, default: :schemas
|
|
18
|
+
base.class_attribute :_component_scopes, default: []
|
|
19
|
+
base.class_attribute :_component_scopes_explicitly_set, default: false
|
|
20
|
+
end
|
|
14
21
|
|
|
15
22
|
Registry.instance.register(base) if base.name
|
|
16
23
|
end
|
|
@@ -28,7 +35,10 @@ module OpenapiRuby
|
|
|
28
35
|
end
|
|
29
36
|
|
|
30
37
|
def schema(definition = nil)
|
|
31
|
-
|
|
38
|
+
if definition
|
|
39
|
+
stringified = deep_stringify(definition)
|
|
40
|
+
self._schema_definition = deep_merge_with_array_concat(_schema_definition, stringified)
|
|
41
|
+
end
|
|
32
42
|
_schema_definition
|
|
33
43
|
end
|
|
34
44
|
|
|
@@ -58,6 +68,11 @@ module OpenapiRuby
|
|
|
58
68
|
self._component_scopes_explicitly_set = true
|
|
59
69
|
end
|
|
60
70
|
|
|
71
|
+
def transform_enum_key(key)
|
|
72
|
+
key = ActiveSupport::Inflector.underscore(key.to_s)
|
|
73
|
+
key.parameterize(separator: "_").upcase
|
|
74
|
+
end
|
|
75
|
+
|
|
61
76
|
def component_name
|
|
62
77
|
(name || "Anonymous").demodulize
|
|
63
78
|
end
|
|
@@ -115,6 +130,21 @@ module OpenapiRuby
|
|
|
115
130
|
!_skip_key_transformation && OpenapiRuby.configuration.camelize_keys
|
|
116
131
|
end
|
|
117
132
|
|
|
133
|
+
# Deep merge that concatenates arrays instead of replacing them,
|
|
134
|
+
# matching the behavior of rswag-schema_components' deeper_merge.
|
|
135
|
+
# This ensures inherited schema properties (like `required`) are merged correctly.
|
|
136
|
+
def deep_merge_with_array_concat(base, override)
|
|
137
|
+
base.merge(override) do |_key, old_val, new_val|
|
|
138
|
+
if old_val.is_a?(Hash) && new_val.is_a?(Hash)
|
|
139
|
+
deep_merge_with_array_concat(old_val, new_val)
|
|
140
|
+
elsif old_val.is_a?(Array) && new_val.is_a?(Array)
|
|
141
|
+
(old_val + new_val).uniq
|
|
142
|
+
else
|
|
143
|
+
new_val
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
118
148
|
def deep_stringify(value)
|
|
119
149
|
case value
|
|
120
150
|
when Hash
|
|
@@ -9,15 +9,20 @@ module OpenapiRuby
|
|
|
9
9
|
transform_keys(hash) { |key| camelize(key) }
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
def transform_keys(value, &block)
|
|
12
|
+
def transform_keys(value, parent_key: nil, &block)
|
|
13
13
|
case value
|
|
14
14
|
when Hash
|
|
15
15
|
value.each_with_object({}) do |(k, v), result|
|
|
16
16
|
new_key = block.call(k.to_s)
|
|
17
|
-
result[new_key] = transform_keys(v, &block)
|
|
17
|
+
result[new_key] = transform_keys(v, parent_key: k.to_s, &block)
|
|
18
18
|
end
|
|
19
19
|
when Array
|
|
20
|
-
|
|
20
|
+
if parent_key == "required"
|
|
21
|
+
# Values in "required" arrays are property names that must also be transformed
|
|
22
|
+
value.map { |v| v.is_a?(String) ? block.call(v) : transform_keys(v, &block) }
|
|
23
|
+
else
|
|
24
|
+
value.map { |v| transform_keys(v, &block) }
|
|
25
|
+
end
|
|
21
26
|
else
|
|
22
27
|
value
|
|
23
28
|
end
|
|
@@ -27,8 +32,12 @@ module OpenapiRuby
|
|
|
27
32
|
key = key.to_s
|
|
28
33
|
return key if key.start_with?("$")
|
|
29
34
|
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
# Preserve leading underscore prefix (e.g., _destroy stays _destroy)
|
|
36
|
+
prefix = key.start_with?("_") ? "_" : ""
|
|
37
|
+
stripped = key.delete_prefix("_")
|
|
38
|
+
|
|
39
|
+
parts = stripped.split("_")
|
|
40
|
+
"#{prefix}#{parts[0]}#{parts[1..].map(&:capitalize).join}"
|
|
32
41
|
end
|
|
33
42
|
end
|
|
34
43
|
end
|
|
@@ -63,77 +63,82 @@ module OpenapiRuby
|
|
|
63
63
|
expanded = File.expand_path(path)
|
|
64
64
|
next unless Dir.exist?(expanded)
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
66
|
+
Dir.glob(File.join(expanded, "**/")).each do |dir_path|
|
|
67
|
+
relative = dir_path.sub("#{expanded}/", "").chomp("/")
|
|
68
|
+
next if relative.empty?
|
|
69
|
+
|
|
70
|
+
const_name = relative.camelize
|
|
71
|
+
const_name.split("::").inject(Object) do |parent, name|
|
|
72
|
+
if parent.const_defined?(name, false)
|
|
73
|
+
parent.const_get(name, false)
|
|
74
|
+
else
|
|
75
|
+
parent.const_set(name, Module.new)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
77
79
|
end
|
|
78
80
|
end
|
|
79
81
|
|
|
80
82
|
def load_component_files!
|
|
81
83
|
scope_paths = OpenapiRuby.configuration.component_scope_paths
|
|
82
84
|
|
|
85
|
+
# Collect all files with their base paths, then sort globally by relative
|
|
86
|
+
# path to ensure consistent load order across multiple base paths.
|
|
87
|
+
# This prevents cross-directory inheritance issues (e.g., a subclass in
|
|
88
|
+
# packs/ai_feedback loading before its superclass in packs/api).
|
|
89
|
+
all_files = collect_all_files
|
|
90
|
+
|
|
83
91
|
if scope_paths.any?
|
|
84
|
-
load_with_scope_inference(scope_paths)
|
|
92
|
+
load_with_scope_inference(all_files, scope_paths)
|
|
85
93
|
else
|
|
86
|
-
|
|
94
|
+
all_files.each { |entry| require entry[:file] }
|
|
87
95
|
end
|
|
88
96
|
end
|
|
89
97
|
|
|
90
|
-
def
|
|
98
|
+
def collect_all_files
|
|
99
|
+
files = []
|
|
91
100
|
@paths.each do |base_path|
|
|
92
101
|
expanded = File.expand_path(base_path)
|
|
93
102
|
next unless Dir.exist?(expanded)
|
|
94
103
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
files.each do |file|
|
|
104
|
+
Dir[File.join(expanded, "**", "*.rb")].each do |file|
|
|
98
105
|
relative = file.sub("#{expanded}/", "")
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
106
|
+
files << {file: file, base_path: expanded, relative: relative}
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
files.sort_by { |entry| entry[:relative] }
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def load_with_scope_inference(all_files, scope_paths)
|
|
113
|
+
all_files.each do |entry|
|
|
114
|
+
inferred_scope = infer_scope(entry[:relative], scope_paths)
|
|
115
|
+
|
|
116
|
+
registered_before = Registry.instance.all_registered_classes.dup
|
|
117
|
+
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)
|
|
117
130
|
end
|
|
118
131
|
end
|
|
119
132
|
end
|
|
120
133
|
end
|
|
121
134
|
|
|
122
135
|
def infer_scope(relative_path, scope_paths)
|
|
123
|
-
# Match longest prefix first for specificity
|
|
124
136
|
scope_paths.sort_by { |prefix, _| -prefix.length }.each do |prefix, scope|
|
|
125
137
|
return scope&.to_sym if relative_path.start_with?("#{prefix}/")
|
|
126
138
|
end
|
|
127
139
|
nil
|
|
128
140
|
end
|
|
129
141
|
|
|
130
|
-
def component_files
|
|
131
|
-
@paths.flat_map do |path|
|
|
132
|
-
expanded = File.expand_path(path)
|
|
133
|
-
Dir[File.join(expanded, "**", "*.rb")]
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
|
-
|
|
137
142
|
def filter_type(type)
|
|
138
143
|
to_openapi_hash[type.to_s] || {}
|
|
139
144
|
end
|
|
@@ -13,21 +13,22 @@ module OpenapiRuby
|
|
|
13
13
|
|
|
14
14
|
def register(component_class)
|
|
15
15
|
type = component_class._component_type
|
|
16
|
-
|
|
16
|
+
name = component_class.name || "Anonymous"
|
|
17
17
|
|
|
18
18
|
@components[type] ||= {}
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
raise DuplicateComponentError, "Component '#{component_class.component_name}' already registered under #{type}"
|
|
22
|
-
end
|
|
20
|
+
check_for_duplicate!(component_class, type)
|
|
23
21
|
|
|
24
|
-
|
|
22
|
+
# Use the full class name as key to avoid collisions between
|
|
23
|
+
# same-named components in different scopes (e.g., Internal::V1::Schemas::PaginatedCollection
|
|
24
|
+
# vs Mobile::V1::Schemas::PaginatedCollection). Scope filtering happens in to_openapi_hash.
|
|
25
|
+
@components[type][name] = component_class
|
|
25
26
|
end
|
|
26
27
|
|
|
27
28
|
def unregister(component_class)
|
|
28
29
|
type = component_class._component_type
|
|
29
|
-
|
|
30
|
-
@components[type]&.delete(
|
|
30
|
+
name = component_class.name || "Anonymous"
|
|
31
|
+
@components[type]&.delete(name)
|
|
31
32
|
end
|
|
32
33
|
|
|
33
34
|
def components_for(type)
|
|
@@ -50,6 +51,41 @@ module OpenapiRuby
|
|
|
50
51
|
@components = {}
|
|
51
52
|
end
|
|
52
53
|
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def check_for_duplicate!(component_class, type)
|
|
57
|
+
short_name = component_class.component_name
|
|
58
|
+
new_scopes = component_class._component_scopes
|
|
59
|
+
new_scopes_set = component_class._component_scopes_explicitly_set
|
|
60
|
+
|
|
61
|
+
@components[type]&.each_value do |existing|
|
|
62
|
+
next if existing.name == component_class.name
|
|
63
|
+
next unless existing.component_name == short_name
|
|
64
|
+
|
|
65
|
+
existing_scopes = existing._component_scopes
|
|
66
|
+
existing_scopes_set = existing._component_scopes_explicitly_set
|
|
67
|
+
|
|
68
|
+
# Skip when exactly one side has explicitly configured scopes — the other
|
|
69
|
+
# is still at its default (freshly included) and may get scopes set later via
|
|
70
|
+
# component_scopes, which unregisters/re-registers and retriggers this check.
|
|
71
|
+
next if new_scopes_set != existing_scopes_set
|
|
72
|
+
|
|
73
|
+
if scopes_overlap?(new_scopes, existing_scopes)
|
|
74
|
+
raise DuplicateComponentError,
|
|
75
|
+
"Component '#{short_name}' is already registered as #{type} " \
|
|
76
|
+
"(existing: #{existing.name}, new: #{component_class.name})"
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def scopes_overlap?(a, b)
|
|
82
|
+
return true if a.empty? && b.empty?
|
|
83
|
+
return true if a.empty? || b.empty?
|
|
84
|
+
(a & b).any?
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
public
|
|
88
|
+
|
|
53
89
|
def to_openapi_hash(scope: nil)
|
|
54
90
|
result = {}
|
|
55
91
|
@components.each do |type, components|
|
data/lib/openapi_ruby/engine.rb
CHANGED
|
@@ -36,21 +36,8 @@ module OpenapiRuby
|
|
|
36
36
|
end
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
config.component_paths.each do |path|
|
|
42
|
-
expanded = Rails.root.join(path)
|
|
43
|
-
next unless expanded.exist?
|
|
44
|
-
|
|
45
|
-
# Auto-define modules for subdirectories (Schemas, Parameters, etc.)
|
|
46
|
-
expanded.children.select(&:directory?).each do |dir|
|
|
47
|
-
mod_name = dir.basename.to_s.camelize.to_sym
|
|
48
|
-
Object.const_set(mod_name, Module.new) unless Object.const_defined?(mod_name)
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
Dir[expanded.join("**", "*.rb")].sort.each { |f| require f }
|
|
52
|
-
end
|
|
53
|
-
end
|
|
39
|
+
# Components are loaded on demand via Components::Loader (e.g. in test helpers),
|
|
40
|
+
# not at boot time — avoids cross-file dependency ordering issues.
|
|
54
41
|
|
|
55
42
|
private
|
|
56
43
|
|
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: 2.
|
|
4
|
+
version: 2.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Morten Hartvig
|
|
@@ -79,12 +79,14 @@ files:
|
|
|
79
79
|
- Rakefile
|
|
80
80
|
- app/controllers/openapi_ruby/schemas_controller.rb
|
|
81
81
|
- app/controllers/openapi_ruby/ui_controller.rb
|
|
82
|
+
- app/views/openapi_ruby/oauth2_redirect.html
|
|
82
83
|
- config/routes.rb
|
|
83
84
|
- lib/generators/openapi_ruby/component/component_generator.rb
|
|
84
85
|
- lib/generators/openapi_ruby/component/templates/component.rb.tt
|
|
85
86
|
- lib/generators/openapi_ruby/install/install_generator.rb
|
|
86
87
|
- lib/generators/openapi_ruby/install/templates/initializer.rb.tt
|
|
87
88
|
- lib/generators/openapi_ruby/install/templates/openapi_helper.rb.tt
|
|
89
|
+
- lib/openapi-ruby.rb
|
|
88
90
|
- lib/openapi_ruby.rb
|
|
89
91
|
- lib/openapi_ruby/adapters/minitest.rb
|
|
90
92
|
- lib/openapi_ruby/adapters/rspec.rb
|
|
@@ -118,13 +120,13 @@ files:
|
|
|
118
120
|
- lib/openapi_ruby/testing/response_validator.rb
|
|
119
121
|
- lib/openapi_ruby/version.rb
|
|
120
122
|
- lib/tasks/openapi_ruby.rake
|
|
121
|
-
homepage: https://github.com/
|
|
123
|
+
homepage: https://github.com/openapi-ruby/openapi-ruby
|
|
122
124
|
licenses:
|
|
123
125
|
- MIT
|
|
124
126
|
metadata:
|
|
125
|
-
homepage_uri: https://github.com/
|
|
126
|
-
source_code_uri: https://github.com/
|
|
127
|
-
changelog_uri: https://github.com/
|
|
127
|
+
homepage_uri: https://github.com/openapi-ruby/openapi-ruby
|
|
128
|
+
source_code_uri: https://github.com/openapi-ruby/openapi-ruby
|
|
129
|
+
changelog_uri: https://github.com/openapi-ruby/openapi-ruby/blob/main/CHANGELOG.md
|
|
128
130
|
rubygems_mfa_required: 'true'
|
|
129
131
|
rdoc_options: []
|
|
130
132
|
require_paths:
|