Skip to content

Commit d4aa844

Browse files
authored
UI scripts now loading dynamiccaly to support contextPath from cookie. (#1626)
Fixed #1624 Swagger UI 5.17.14 OpenApi Explorer 2.2.720 Redoc 2.1.5
1 parent b7a1048 commit d4aa844

File tree

14 files changed

+467
-388
lines changed

14 files changed

+467
-388
lines changed

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
projectVersion=6.11.1-SNAPSHOT
1+
projectVersion=6.12.0-SNAPSHOT
22
projectGroup=io.micronaut.openapi
33

44
title=OpenAPI/Swagger Support

openapi/src/main/java/io/micronaut/openapi/view/RapiPDFConfig.java

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,21 @@
1515
*/
1616
package io.micronaut.openapi.view;
1717

18-
import java.util.Collections;
19-
import java.util.HashMap;
20-
import java.util.List;
21-
import java.util.Locale;
22-
import java.util.Map;
23-
import java.util.function.Function;
24-
2518
import io.micronaut.core.annotation.Nullable;
2619
import io.micronaut.core.util.StringUtils;
2720
import io.micronaut.inject.visitor.VisitorContext;
2821
import io.micronaut.openapi.view.OpenApiViewConfig.RendererType;
2922
import io.micronaut.openapi.visitor.Pair;
3023
import io.micronaut.openapi.visitor.group.OpenApiInfo;
3124

25+
import java.util.Collections;
26+
import java.util.HashMap;
27+
import java.util.List;
28+
import java.util.Locale;
29+
import java.util.Map;
30+
import java.util.function.Function;
31+
32+
import static io.micronaut.openapi.view.OpenApiViewConfig.replacePlaceHolder;
3233
import static io.micronaut.openapi.visitor.StringUtil.SLASH;
3334

3435
/**
@@ -44,7 +45,7 @@ final class RapiPDFConfig extends AbstractViewConfig {
4445
DEFAULT_RAPIPDF_JS_PATH + "rapipdf-min.js"
4546
);
4647

47-
private static final String LINK = "<script src='{{rapipdf.js.url.prefix}}rapipdf-min.js'></script>";
48+
private static final String SCRIPT_RAPIPDF = "script(contextPath + \"{{rapipdf.js.url.prefix}}rapipdf-min.js\", head)";
4849
private static final String TAG = "<rapi-pdf id='rapi-pdf' {{rapipdf.attributes}}></rapi-pdf>";
4950
private static final String SPEC = "document.getElementById('rapi-pdf').setAttribute('spec-url', contextPath + '{{specURL}}');";
5051
private static final Map<String, Object> DEFAULT_OPTIONS = new HashMap<>(6);
@@ -112,7 +113,6 @@ public boolean isEnabled() {
112113
* @param properties A set of properties.
113114
* @param openApiInfos Open API info objects.
114115
* @param context Visitor context.
115-
*
116116
* @return A RapipdfConfig.
117117
*/
118118
static RapiPDFConfig fromProperties(Map<String, String> properties, Map<Pair<String, String>, OpenApiInfo> openApiInfos, VisitorContext context) {
@@ -127,7 +127,6 @@ static RapiPDFConfig fromProperties(Map<String, String> properties, Map<Pair<Str
127127
* @param template A template.
128128
* @param rendererType The renderer type.
129129
* @param context Visitor context.
130-
*
131130
* @return The template with placeholders replaced.
132131
*/
133132
String render(String template, RendererType rendererType, VisitorContext context) {
@@ -145,18 +144,18 @@ String render(String template, RendererType rendererType, VisitorContext context
145144
options.put("style", DEFAULT_RAPIDOC_STYLE);
146145
}
147146
}
148-
String script = OpenApiViewConfig.replacePlaceHolder(LINK, "rapipdf.js.url.prefix", isDefaultJsUrl ? getFinalUrlPrefix(rendererType, context) : jsUrl, StringUtils.EMPTY_STRING);
149-
String rapipdfTag = OpenApiViewConfig.replacePlaceHolder(TAG, "rapipdf.attributes", toHtmlAttributes(), StringUtils.EMPTY_STRING);
147+
String script = replacePlaceHolder(SCRIPT_RAPIPDF, "rapipdf.js.url.prefix", isDefaultJsUrl ? getFinalUrlPrefix(rendererType, context) : jsUrl, StringUtils.EMPTY_STRING);
148+
String rapipdfTag = replacePlaceHolder(TAG, "rapipdf.attributes", toHtmlAttributes(), StringUtils.EMPTY_STRING);
150149
if (styleUpdated) {
151150
options.remove("style");
152151
}
153-
template = OpenApiViewConfig.replacePlaceHolder(template, "rapipdf.script", script, StringUtils.EMPTY_STRING);
154-
template = OpenApiViewConfig.replacePlaceHolder(template, "rapipdf.specurl", SPEC, StringUtils.EMPTY_STRING);
155-
return OpenApiViewConfig.replacePlaceHolder(template, "rapipdf.tag", rapipdfTag, StringUtils.EMPTY_STRING);
152+
template = replacePlaceHolder(template, "rapipdf.script", script, StringUtils.EMPTY_STRING);
153+
template = replacePlaceHolder(template, "rapipdf.specurl", SPEC, StringUtils.EMPTY_STRING);
154+
return replacePlaceHolder(template, "rapipdf.tag", rapipdfTag, StringUtils.EMPTY_STRING);
156155
} else {
157-
template = OpenApiViewConfig.replacePlaceHolder(template, "rapipdf.script", StringUtils.EMPTY_STRING, StringUtils.EMPTY_STRING);
158-
template = OpenApiViewConfig.replacePlaceHolder(template, "rapipdf.specurl", StringUtils.EMPTY_STRING, StringUtils.EMPTY_STRING);
159-
return OpenApiViewConfig.replacePlaceHolder(template, "rapipdf.tag", StringUtils.EMPTY_STRING, StringUtils.EMPTY_STRING);
156+
template = replacePlaceHolder(template, "rapipdf.script", StringUtils.EMPTY_STRING, StringUtils.EMPTY_STRING);
157+
template = replacePlaceHolder(template, "rapipdf.specurl", StringUtils.EMPTY_STRING, StringUtils.EMPTY_STRING);
158+
return replacePlaceHolder(template, "rapipdf.tag", StringUtils.EMPTY_STRING, StringUtils.EMPTY_STRING);
160159
}
161160
}
162161

openapi/src/main/java/io/micronaut/openapi/view/RedocConfig.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,19 @@
1515
*/
1616
package io.micronaut.openapi.view;
1717

18-
import java.util.Collections;
19-
import java.util.HashMap;
20-
import java.util.List;
21-
import java.util.Map;
22-
import java.util.function.Function;
23-
2418
import io.micronaut.core.annotation.Nullable;
2519
import io.micronaut.core.util.StringUtils;
2620
import io.micronaut.inject.visitor.VisitorContext;
2721
import io.micronaut.openapi.view.OpenApiViewConfig.RendererType;
2822
import io.micronaut.openapi.visitor.Pair;
2923
import io.micronaut.openapi.visitor.group.OpenApiInfo;
3024

25+
import java.util.Collections;
26+
import java.util.HashMap;
27+
import java.util.List;
28+
import java.util.Map;
29+
import java.util.function.Function;
30+
3131
import static io.micronaut.openapi.visitor.StringUtil.SLASH;
3232

3333
/**
@@ -144,7 +144,6 @@ private RedocConfig(Map<Pair<String, String>, OpenApiInfo> openApiInfos) {
144144
* @param properties A set of properties.
145145
* @param openApiInfos Open API info objects.
146146
* @param context Visitor context.
147-
*
148147
* @return A RedocConfig.
149148
*/
150149
static RedocConfig fromProperties(Map<String, String> properties, Map<Pair<String, String>, OpenApiInfo> openApiInfos, VisitorContext context) {

openapi/src/main/java/io/micronaut/openapi/view/SwaggerUIConfig.java

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,6 @@
1515
*/
1616
package io.micronaut.openapi.view;
1717

18-
import java.util.Collections;
19-
import java.util.HashMap;
20-
import java.util.List;
21-
import java.util.Locale;
22-
import java.util.Map;
23-
import java.util.function.Function;
24-
import java.util.stream.Collectors;
25-
2618
import io.micronaut.core.annotation.NonNull;
2719
import io.micronaut.core.annotation.Nullable;
2820
import io.micronaut.core.util.CollectionUtils;
@@ -32,6 +24,15 @@
3224
import io.micronaut.openapi.visitor.Pair;
3325
import io.micronaut.openapi.visitor.group.OpenApiInfo;
3426

27+
import java.util.Collections;
28+
import java.util.HashMap;
29+
import java.util.List;
30+
import java.util.Locale;
31+
import java.util.Map;
32+
import java.util.function.Function;
33+
import java.util.stream.Collectors;
34+
35+
import static io.micronaut.openapi.view.OpenApiViewConfig.replacePlaceHolder;
3536
import static io.micronaut.openapi.visitor.StringUtil.DOT;
3637
import static io.micronaut.openapi.visitor.StringUtil.SLASH;
3738

@@ -45,6 +46,7 @@ final class SwaggerUIConfig extends AbstractViewConfig {
4546
private static final String DEFAULT_SWAGGER_JS_PATH = OpenApiViewConfig.RESOURCE_DIR + SLASH;
4647

4748
private static final List<String> RESOURCE_FILES = List.of(
49+
DEFAULT_SWAGGER_JS_PATH + "index.css",
4850
DEFAULT_SWAGGER_JS_PATH + "swagger-ui.css",
4951
DEFAULT_SWAGGER_JS_PATH + "favicon-16x16.png",
5052
DEFAULT_SWAGGER_JS_PATH + "favicon-32x32.png",
@@ -238,7 +240,6 @@ static boolean hasOauth2Option(Map<String, Object> options) {
238240
* @param properties A set of properties.
239241
* @param openApiInfos Open API info objects.
240242
* @param context Visitor context.
241-
*
242243
* @return A SwaggerUIConfig.
243244
*/
244245
static SwaggerUIConfig fromProperties(Map<String, String> properties, Map<Pair<String, String>, OpenApiInfo> openApiInfos, VisitorContext context) {
@@ -265,10 +266,10 @@ public String render(String template, @Nullable VisitorContext context) {
265266
String finalUrlPrefix = getFinalUrlPrefix(RendererType.SWAGGER_UI, context);
266267

267268
template = rapiPDFConfig.render(template, RendererType.SWAGGER_UI, context);
268-
template = OpenApiViewConfig.replacePlaceHolder(template, PREFIX_SWAGGER_UI + ".js.url.prefix", isDefaultJsUrl ? finalUrlPrefix : jsUrl, StringUtils.EMPTY_STRING);
269-
template = OpenApiViewConfig.replacePlaceHolder(template, PREFIX_SWAGGER_UI + ".attributes", toOptions(), StringUtils.EMPTY_STRING);
269+
template = replacePlaceHolder(template, PREFIX_SWAGGER_UI + ".js.url.prefix", isDefaultJsUrl ? finalUrlPrefix : jsUrl, StringUtils.EMPTY_STRING);
270+
template = replacePlaceHolder(template, PREFIX_SWAGGER_UI + ".attributes", toOptions(), StringUtils.EMPTY_STRING);
270271
template = template.replace("{{" + PREFIX_SWAGGER_UI + ".theme}}", theme == null || Theme.CLASSIC == theme ? StringUtils.EMPTY_STRING :
271-
"<link rel='stylesheet' type='text/css' href='" + (isDefaultThemeUrl ? finalUrlPrefix + theme.getCss() + ".css" : themeUrl) + "' />");
272+
"link(contextPath + \"" + (isDefaultThemeUrl ? finalUrlPrefix + theme.getCss() + ".css" : themeUrl) + "\", head, \"text/css\", \"stylesheet\")");
272273
template = template.replace("{{" + PREFIX_SWAGGER_UI + DOT + OPTION_OAUTH2 + "}}", hasOauth2Option(options) ? toOauth2Options() : StringUtils.EMPTY_STRING);
273274
template = template.replace("{{" + PREFIX_SWAGGER_UI + DOT + OPTION_PRIMARY_NAME + "}}", StringUtils.isNotEmpty(primaryName) ? getPrimaryName(context) : StringUtils.EMPTY_STRING);
274275
template = template.replace("{{" + PREFIX_SWAGGER_UI + DOT + OPTION_URLS + "}}", getUrlStr(context));

openapi/src/main/resources/templates/openapi-explorer/index.html

Lines changed: 64 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,6 @@
44
<meta charset="utf-8">
55
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1,user-scalable=yes">
66
<title>OpenAPI Explorer</title>
7-
<link rel="stylesheet" href="{{openapi-explorer.js.url.prefix}}default.min.css">
8-
<link rel="stylesheet" href="{{openapi-explorer.js.url.prefix}}bootstrap.min.css" crossorigin="anonymous">
9-
<link rel="stylesheet" href="{{openapi-explorer.js.url.prefix}}font-awesome.min.css">
10-
<script src="{{openapi-explorer.js.url.prefix}}openapi-explorer.min.js" type="module" defer></script>
117
<style>
128
html, body {
139
height: 100%
@@ -66,7 +62,6 @@
6662
</head>
6763
<body>
6864

69-
7065
<nav class="navbar navbar-dark bg-dark">
7166
<div class="container-fluid w-100 d-flex justify-content-between align-items-center">
7267
<div class="">
@@ -98,43 +93,75 @@
9893
<!--collapse table schema-description-expanded="true" nav-item-spacing="compact" show-components="true" bg-color="#FFFFFF" header-bg-color="#DEE2E6" nav-bg-color="#1D2F3B" text-color="#465865" nav-hover-text-color="#FFFFFF" primary-color="#1D2F3B" secondary-color="#FBAF0B"-->
9994
<script>
10095
const extract = function(v) {
101-
return decodeURIComponent(v.replace(/(?:(?:^|.*;\s*)contextPath\s*\=\s*([^;]*).*$)|^.*$/, "$1"));
102-
},
103-
cookie = extract(document.cookie),
104-
contextPath = cookie === '' ? extract(window.location.search.substring(1)) : cookie,
105-
openApiExplorer = document.getElementById('openapi-explorer');
106-
let specUrl = '{{specURL}}';
107-
if (contextPath !== '') {
108-
specUrl = contextPath + '{{specURL}}';
96+
return decodeURIComponent(v.replace(/(?:^|.*;\s*)contextPath\s*=\s*([^;]*).*$|^.*$/, "$1"));
97+
}
98+
const cookie = extract(document.cookie)
99+
const contextPath = cookie === '' ? extract(window.location.search.substring(1)) : cookie
100+
const openApiExplorer = document.getElementById('openapi-explorer');
101+
const head = document.getElementsByTagName('head')[0]
102+
103+
link(contextPath + "{{openapi-explorer.js.url.prefix}}default.min.css", head, "text/css", "stylesheet")
104+
link(contextPath + "{{openapi-explorer.js.url.prefix}}bootstrap.min.css", head, "text/css", "stylesheet", "anonymous")
105+
link(contextPath + "{{openapi-explorer.js.url.prefix}}font-awesome.min.css", head, "text/css", "stylesheet")
106+
const openapiExplorerJs = script(contextPath + "{{openapi-explorer.js.url.prefix}}openapi-explorer.min.js", head, "module", true)
107+
108+
openapiExplorerJs.onload = function () {
109+
let specUrl = '{{specURL}}';
110+
if (contextPath !== '') {
111+
specUrl = contextPath + '{{specURL}}';
112+
openApiExplorer.setAttribute('spec-url', specUrl);
113+
openApiExplorer.addEventListener('spec-loaded', e => {
114+
e.detail.tags.forEach(tag => tag.paths.forEach(path => path.path = contextPath + path.path));
115+
openApiExplorer.requestUpdate();
116+
});
117+
}
109118
openApiExplorer.setAttribute('spec-url', specUrl);
110-
openApiExplorer.addEventListener('spec-loaded', e => {
111-
e.detail.tags.forEach(tag => tag.paths.forEach(path => path.path = contextPath + path.path));
112-
openApiExplorer.requestUpdate();
119+
document.addEventListener('DOMContentLoaded', async () => {
120+
const specUrl = document.getElementById('specUrl');
121+
const currentSpecUrl = new URLSearchParams(window.location.search);
122+
const newSpecUrl = currentSpecUrl.get('specUrl') || currentSpecUrl.get('url') || currentSpecUrl.get('spec');
123+
if (newSpecUrl) {
124+
document.getElementsByTagName("openapi-explorer")[0].setAttribute('spec-url', newSpecUrl);
125+
specUrl.value = newSpecUrl;
126+
}
127+
128+
const loadButton = document.getElementById('loadButton');
129+
loadButton.addEventListener('click', () => {
130+
try {
131+
const newUrl = new URL(window.location);
132+
newUrl.searchParams.set('url', specUrl.value);
133+
window.location.assign(newUrl.toString());
134+
document.getElementsByTagName("openapi-explorer")[0].setAttribute('spec-url', specUrl.value);
135+
} catch (error) {
136+
console.error('Failed to set spec url into url address bar', error);
137+
}
138+
});
113139
});
114140
}
115-
openApiExplorer.setAttribute('spec-url', specUrl);
116-
document.addEventListener('DOMContentLoaded', async () => {
117-
const specUrl = document.getElementById('specUrl');
118-
const currentSpecUrl = new URLSearchParams(window.location.search);
119-
const newSpecUrl = currentSpecUrl.get('specUrl') || currentSpecUrl.get('url') || currentSpecUrl.get('spec');
120-
if (newSpecUrl) {
121-
document.getElementsByTagName("openapi-explorer")[0].setAttribute('spec-url', newSpecUrl);
122-
specUrl.value = newSpecUrl;
141+
142+
function link(href, head, type, rel, crossorigin) {
143+
const el = document.createElement('link');
144+
el.href = href;
145+
el.type = type;
146+
el.rel = rel;
147+
if (crossorigin !== undefined && crossorigin) {
148+
el.crossorigin = crossorigin;
123149
}
150+
head.appendChild(el);
151+
}
124152

125-
const loadButton = document.getElementById('loadButton');
126-
loadButton.addEventListener('click', () => {
127-
try {
128-
const newUrl = new URL(window.location);
129-
newUrl.searchParams.set('url', specUrl.value);
130-
window.location.assign(newUrl.toString());
131-
document.getElementsByTagName("openapi-explorer")[0].setAttribute('spec-url', specUrl.value);
132-
} catch (error) {
133-
console.error('Failed to set spec url into url address bar', error);
134-
}
135-
});
136-
});
137-
{{rapipdf.specurl}}
153+
function script(src, head, type, defer) {
154+
const el = document.createElement('script');
155+
el.src = src;
156+
if (type !== undefined && type) {
157+
el.type = type;
158+
}
159+
if (defer !== undefined && defer) {
160+
el.setAttribute("defer", "");
161+
}
162+
head.appendChild(el);
163+
return el;
164+
}
138165
</script>
139166

140167
</body>

openapi/src/main/resources/templates/openapi-explorer/res/openapi-explorer.min.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

openapi/src/main/resources/templates/rapidoc/index.html

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
<!-- Important: The Custom element uses utf8 characters -->
77
<meta charset='utf-8' />
88
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1,user-scalable=yes">
9-
<script defer="defer" src='{{rapidoc.js.url.prefix}}rapidoc-min.js'></script>
10-
{{rapipdf.script}}
119
</head>
1210
<script>
1311
window.addEventListener('DOMContentLoaded', () => {
@@ -21,19 +19,36 @@
2119
<rapi-doc id='rapidoc' {{rapidoc.attributes}}>{{rapipdf.tag}}</rapi-doc>
2220
<script>
2321
const extract = function(v) {
24-
return decodeURIComponent(v.replace(/(?:(?:^|.*;\s*)contextPath\s*\=\s*([^;]*).*$)|^.*$/, "$1"));
25-
},
26-
cookie = extract(document.cookie),
27-
contextPath = cookie === '' ? extract(window.location.search.substring(1)) : cookie,
28-
rapidoc = document.getElementById('rapidoc');
29-
if (contextPath !== '') {
30-
rapidoc.addEventListener('spec-loaded', e => {
31-
e.detail.tags.forEach(tag => tag.paths.forEach(path => path.path = contextPath + path.path));
32-
rapidoc.requestUpdate();
33-
});
22+
return decodeURIComponent(v.replace(/(?:^|.*;\s*)contextPath\s*=\s*([^;]*).*$|^.*$/, "$1"));
23+
}
24+
const cookie = extract(document.cookie)
25+
const contextPath = cookie === '' ? extract(window.location.search.substring(1)) : cookie
26+
const head = document.getElementsByTagName('head')[0];
27+
{{rapipdf.script}}
28+
const rapidocJs = script(contextPath + "{{rapidoc.js.url.prefix}}rapidoc-min.js", head, true)
29+
30+
rapidocJs.onload = function () {
31+
32+
const rapidoc = document.getElementById('rapidoc')
33+
if (contextPath !== '') {
34+
rapidoc.addEventListener('spec-loaded', e => {
35+
e.detail.tags.forEach(tag => tag.paths.forEach(path => path.path = contextPath + path.path));
36+
rapidoc.requestUpdate();
37+
});
38+
}
39+
rapidoc.setAttribute('spec-url', contextPath + '{{specURL}}');
40+
{{rapipdf.specurl}}
41+
}
42+
43+
function script(src, head, type, defer) {
44+
const el = document.createElement('script');
45+
el.src = src;
46+
if (defer !== undefined && defer) {
47+
el.setAttribute("defer", "");
48+
}
49+
head.appendChild(el);
50+
return el;
3451
}
35-
rapidoc.setAttribute('spec-url', contextPath + '{{specURL}}');
36-
{{rapipdf.specurl}}
3752
</script>
3853
</body>
3954
</html>

0 commit comments

Comments
 (0)