Skip to content

Commit e0feb04

Browse files
committed
Adding initial Scalar Support. Fixes #3084
1 parent 31ba90b commit e0feb04

File tree

122 files changed

+5416
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

122 files changed

+5416
-0
lines changed

pom.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
<module>springdoc-openapi-starter-webflux-api</module>
4545
<module>springdoc-openapi-starter-webmvc-ui</module>
4646
<module>springdoc-openapi-starter-webflux-ui</module>
47+
<module>springdoc-openapi-starter-webmvc-scalar</module>
48+
<module>springdoc-openapi-starter-webflux-scalar</module>
4749
<module>springdoc-openapi-bom</module>
4850
</modules>
4951

@@ -61,6 +63,7 @@
6163
<spring-cloud-function.version>4.2.2</spring-cloud-function.version>
6264
<spring-security-oauth2-authorization-server.version>1.4.3
6365
</spring-security-oauth2-authorization-server.version>
66+
<scalar.version>0.1.0</scalar.version>
6467
</properties>
6568

6669
<dependencyManagement>
@@ -96,6 +99,11 @@
9699
<artifactId>spring-security-oauth2-authorization-server</artifactId>
97100
<version>${spring-security-oauth2-authorization-server.version}</version>
98101
</dependency>
102+
<dependency>
103+
<groupId>com.scalar.maven</groupId>
104+
<artifactId>scalar</artifactId>
105+
<version>${scalar.version}</version>
106+
</dependency>
99107
</dependencies>
100108
</dependencyManagement>
101109
<dependencies>

springdoc-openapi-starter-common/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@
9393
<artifactId>querydsl-core</artifactId>
9494
<optional>true</optional>
9595
</dependency>
96+
<dependency>
97+
<groupId>com.scalar.maven</groupId>
98+
<artifactId>scalar</artifactId>
99+
<optional>true</optional>
100+
</dependency>
96101
</dependencies>
97102
<build>
98103
<resources>
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
/*
2+
*
3+
* *
4+
* * *
5+
* * * *
6+
* * * * *
7+
* * * * * * Copyright 2019-2025 the original author or authors.
8+
* * * * * *
9+
* * * * * * Licensed under the Apache License, Version 2.0 (the "License");
10+
* * * * * * you may not use this file except in compliance with the License.
11+
* * * * * * You may obtain a copy of the License at
12+
* * * * * *
13+
* * * * * * https://www.apache.org/licenses/LICENSE-2.0
14+
* * * * * *
15+
* * * * * * Unless required by applicable law or agreed to in writing, software
16+
* * * * * * distributed under the License is distributed on an "AS IS" BASIS,
17+
* * * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
* * * * * * See the License for the specific language governing permissions and
19+
* * * * * * limitations under the License.
20+
* * * * *
21+
* * * *
22+
* * *
23+
* *
24+
*
25+
*/
26+
27+
package org.springdoc.scalar;
28+
29+
import java.io.IOException;
30+
import java.io.InputStream;
31+
import java.net.URLDecoder;
32+
import java.nio.charset.StandardCharsets;
33+
34+
import com.scalar.maven.webjar.ScalarProperties;
35+
36+
import org.springframework.http.MediaType;
37+
import org.springframework.http.ResponseEntity;
38+
39+
import static org.springdoc.scalar.ScalarConstants.SCALAR_DEFAULT_URL;
40+
import static org.springdoc.scalar.ScalarConstants.SCALAR_JS_FILENAME;
41+
import static org.springframework.util.AntPathMatcher.DEFAULT_PATH_SEPARATOR;
42+
43+
/**
44+
* The type Abstract scalar controller.
45+
*
46+
* @author bnasslahsen This is a copy of the class <a href="com.scalar.maven.webjar.ScalarController">ScalarController</a> from the scalar webjar. It has been slightly modified to fit the springdoc-openapi code base.
47+
*/
48+
public abstract class AbstractScalarController {
49+
50+
/**
51+
* The Scalar properties.
52+
*/
53+
protected final ScalarProperties scalarProperties;
54+
55+
/**
56+
* The Original scalar url.
57+
*/
58+
protected final String originalScalarUrl;
59+
60+
/**
61+
* Instantiates a new Abstract scalar controller.
62+
*
63+
* @param scalarProperties the scalar properties
64+
*/
65+
protected AbstractScalarController(ScalarProperties scalarProperties) {
66+
this.scalarProperties = scalarProperties;
67+
this.originalScalarUrl = scalarProperties.getUrl();
68+
}
69+
70+
/**
71+
* Serves the main API reference interface.
72+
* <p>This endpoint returns an HTML page that displays the Scalar API Reference
73+
* interface. The page is configured with the OpenAPI specification URL from
74+
* the properties.</p>
75+
*
76+
* @param requestUrl the request url
77+
* @return a ResponseEntity containing the HTML content for the API reference interface
78+
* @throws IOException if the HTML template cannot be loaded
79+
*/
80+
protected ResponseEntity<String> getDocs(String requestUrl) throws IOException {
81+
// Load the template HTML
82+
InputStream inputStream = getClass().getResourceAsStream("/META-INF/resources/webjars/scalar/index.html");
83+
if (inputStream == null) {
84+
return ResponseEntity.notFound().build();
85+
}
86+
87+
String html = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
88+
requestUrl = decode(requestUrl);
89+
// Replace the placeholders with actual values
90+
String cdnUrl = buildJsBundleUrl(requestUrl);
91+
String injectedHtml = html
92+
.replace("__JS_BUNDLE_URL__", cdnUrl)
93+
.replace("__CONFIGURATION__", """
94+
{
95+
url: "%s"
96+
}
97+
""".formatted(buildApiDocsUrl(requestUrl)));
98+
99+
return ResponseEntity.ok()
100+
.contentType(MediaType.TEXT_HTML)
101+
.body(injectedHtml);
102+
}
103+
104+
/**
105+
* Serves the JavaScript bundle for the Scalar API Reference.
106+
* <p>This endpoint returns the JavaScript file that powers the Scalar API Reference
107+
* interface. The file is served with the appropriate MIME type.</p>
108+
*
109+
* @return a ResponseEntity containing the JavaScript bundle
110+
* @throws IOException if the JavaScript file cannot be loaded
111+
*/
112+
protected ResponseEntity<byte[]> getScalarJs() throws IOException {
113+
// Load the scalar.js file
114+
InputStream inputStream = getClass().getResourceAsStream("/META-INF/resources/webjars/scalar/" + SCALAR_JS_FILENAME);
115+
if (inputStream == null) {
116+
return ResponseEntity.notFound().build();
117+
}
118+
119+
byte[] jsContent = inputStream.readAllBytes();
120+
121+
return ResponseEntity.ok()
122+
.contentType(MediaType.valueOf("application/javascript"))
123+
.body(jsContent);
124+
}
125+
126+
/**
127+
* Decode string.
128+
*
129+
* @param requestURI the request uri
130+
* @return the string
131+
*/
132+
protected String decode(String requestURI) {
133+
return URLDecoder.decode(requestURI, StandardCharsets.UTF_8);
134+
}
135+
136+
/**
137+
* Gets api docs url.
138+
*
139+
* @param requestUrl the request url
140+
* @param apiDocsPath the api docs path
141+
* @return the api docs url
142+
*/
143+
protected String buildApiDocsUrl(String requestUrl, String apiDocsPath) {
144+
String apiDocsUrl = scalarProperties.getUrl();
145+
if (SCALAR_DEFAULT_URL.equals(originalScalarUrl)) {
146+
String serverUrl = requestUrl.substring(0, requestUrl.length() - scalarProperties.getPath().length());
147+
apiDocsUrl = serverUrl + apiDocsPath;
148+
}
149+
return apiDocsUrl;
150+
}
151+
152+
/**
153+
* Build js bundle url string.
154+
*
155+
* @param requestUrl the request url
156+
* @param scalarPath the scalar path
157+
* @return the string
158+
*/
159+
protected String buildJsBundleUrl(String requestUrl, String scalarPath) {
160+
if (SCALAR_DEFAULT_URL.equals(originalScalarUrl)) {
161+
int firstPathSlash = requestUrl.indexOf('/', requestUrl.indexOf("://") + 3);
162+
String path = firstPathSlash >= 0 ? requestUrl.substring(firstPathSlash) : "/";
163+
if( path.endsWith("/"))
164+
path = path.substring(0, path.length() - 1);
165+
return path + DEFAULT_PATH_SEPARATOR + SCALAR_JS_FILENAME;
166+
}
167+
return scalarPath + DEFAULT_PATH_SEPARATOR + SCALAR_JS_FILENAME;
168+
}
169+
170+
/**
171+
* Gets api docs url.
172+
*
173+
* @param requestUrl the request url
174+
* @return the api docs url
175+
*/
176+
protected abstract String buildApiDocsUrl(String requestUrl);
177+
178+
/**
179+
* Build js bundle url string.
180+
*
181+
* @param requestUrl the request url
182+
* @return the string
183+
*/
184+
protected abstract String buildJsBundleUrl(String requestUrl);
185+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
*
3+
* *
4+
* * *
5+
* * * *
6+
* * * * *
7+
* * * * * * Copyright 2019-2025 the original author or authors.
8+
* * * * * *
9+
* * * * * * Licensed under the Apache License, Version 2.0 (the "License");
10+
* * * * * * you may not use this file except in compliance with the License.
11+
* * * * * * You may obtain a copy of the License at
12+
* * * * * *
13+
* * * * * * https://www.apache.org/licenses/LICENSE-2.0
14+
* * * * * *
15+
* * * * * * Unless required by applicable law or agreed to in writing, software
16+
* * * * * * distributed under the License is distributed on an "AS IS" BASIS,
17+
* * * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
* * * * * * See the License for the specific language governing permissions and
19+
* * * * * * limitations under the License.
20+
* * * * *
21+
* * * *
22+
* * *
23+
* *
24+
*
25+
*/
26+
27+
package org.springdoc.scalar;
28+
29+
/**
30+
* The type ScalarConstants.
31+
*
32+
* @author bnasslahsen
33+
*/
34+
public class ScalarConstants {
35+
36+
/**
37+
* The constant SCALAR_DEFAULT_PATH.
38+
*/
39+
public static final String SCALAR_DEFAULT_PATH = "/scalar";
40+
41+
/**
42+
* The constant SCALAR_JS_FILENAME.
43+
*/
44+
public static final String SCALAR_JS_FILENAME = "scalar.js";
45+
46+
/**
47+
* The constant SCALAR_DEFAULT_URL.
48+
*/
49+
public static final String SCALAR_DEFAULT_URL = "https://cdn.jsdelivr.net/npm/@scalar/galaxy/dist/latest.json";
50+
51+
/**
52+
* The constant DEFAULT_SCALAR_ACTUATOR_PATH.
53+
*/
54+
public static final String DEFAULT_SCALAR_ACTUATOR_PATH = "scalar";
55+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
*
3+
* *
4+
* * *
5+
* * * *
6+
* * * * *
7+
* * * * * * Copyright 2019-2025 the original author or authors.
8+
* * * * * *
9+
* * * * * * Licensed under the Apache License, Version 2.0 (the "License");
10+
* * * * * * you may not use this file except in compliance with the License.
11+
* * * * * * You may obtain a copy of the License at
12+
* * * * * *
13+
* * * * * * https://www.apache.org/licenses/LICENSE-2.0
14+
* * * * * *
15+
* * * * * * Unless required by applicable law or agreed to in writing, software
16+
* * * * * * distributed under the License is distributed on an "AS IS" BASIS,
17+
* * * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
* * * * * * See the License for the specific language governing permissions and
19+
* * * * * * limitations under the License.
20+
* * * * *
21+
* * * *
22+
* * *
23+
* *
24+
*
25+
*/
26+
27+
package org.springdoc.scalar;
28+
29+
/**
30+
* @author bnasslahsen
31+
*/
32+
33+
import org.springframework.boot.autoconfigure.AutoConfigurationImportFilter;
34+
import org.springframework.boot.autoconfigure.AutoConfigurationMetadata;
35+
36+
/**
37+
* Disable Scalar WebJar AutoConfiguration.
38+
*/
39+
public class ScalarDisableAutoConfiguration implements AutoConfigurationImportFilter {
40+
41+
/**
42+
* The constant TARGET.
43+
*/
44+
private static final String TARGET = "com.scalar.maven.webjar.ScalarAutoConfiguration";
45+
46+
@Override
47+
public boolean[] match(String[] candidates, AutoConfigurationMetadata metadata) {
48+
boolean[] matches = new boolean[candidates.length];
49+
for (int i = 0; i < candidates.length; i++) {
50+
String candidate = candidates[i];
51+
// keep everything except the target
52+
matches[i] = !TARGET.equals(candidate);
53+
}
54+
return matches;
55+
}
56+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
2+
org.springdoc.scalar.ScalarDisableAutoConfiguration

0 commit comments

Comments
 (0)