2121 * * * *
2222 * * *
2323 * *
24- *
24+ *
2525 */
2626
2727package org .springdoc .scalar ;
3030import java .io .InputStream ;
3131import java .net .URLDecoder ;
3232import java .nio .charset .StandardCharsets ;
33- import java .util .List ;
34- import java .util .stream .Collectors ;
3533
34+ import com .fasterxml .jackson .core .JsonProcessingException ;
35+ import com .fasterxml .jackson .databind .ObjectMapper ;
3636import com .scalar .maven .webjar .ScalarProperties ;
37- import com .scalar .maven .webjar .ScalarProperties .ScalarSource ;
37+ import com .scalar .maven .webjar .internal .ScalarConfiguration ;
38+ import com .scalar .maven .webjar .internal .ScalarConfigurationMapper ;
3839
3940import org .springframework .http .MediaType ;
4041import org .springframework .http .ResponseEntity ;
4142
43+ import static org .springdoc .scalar .ScalarConstants .HTML_TEMPLATE_PATH ;
4244import static org .springdoc .scalar .ScalarConstants .SCALAR_DEFAULT_URL ;
4345import static org .springdoc .scalar .ScalarConstants .SCALAR_JS_FILENAME ;
4446import static org .springframework .util .AntPathMatcher .DEFAULT_PATH_SEPARATOR ;
@@ -60,14 +62,21 @@ public abstract class AbstractScalarController {
6062 */
6163 protected final String originalScalarUrl ;
6264
65+ /**
66+ * The Object mapper.
67+ */
68+ private final ObjectMapper objectMapper ;
69+
6370 /**
6471 * Instantiates a new Abstract scalar controller.
6572 *
6673 * @param scalarProperties the scalar properties
74+ * @param objectMapper the object mapper
6775 */
68- protected AbstractScalarController (ScalarProperties scalarProperties ) {
76+ protected AbstractScalarController (ScalarProperties scalarProperties , ObjectMapper objectMapper ) {
6977 this .scalarProperties = scalarProperties ;
7078 this .originalScalarUrl = scalarProperties .getUrl ();
79+ this .objectMapper = objectMapper ;
7180 }
7281
7382 /**
@@ -82,22 +91,18 @@ protected AbstractScalarController(ScalarProperties scalarProperties) {
8291 */
8392 protected ResponseEntity <String > getDocs (String requestUrl ) throws IOException {
8493 // Load the template HTML
85- InputStream inputStream = getClass ().getResourceAsStream ("/META-INF/resources/webjars/scalar/index.html" );
94+ InputStream inputStream = getClass ().getResourceAsStream (HTML_TEMPLATE_PATH );
8695 if (inputStream == null ) {
87- return ResponseEntity . notFound (). build ( );
96+ throw new IOException ( "HTML template not found at: " + HTML_TEMPLATE_PATH );
8897 }
8998
9099 String html = new String (inputStream .readAllBytes (), StandardCharsets .UTF_8 );
91- requestUrl = decode ( requestUrl );
100+
92101 // Replace the placeholders with actual values
93- String cdnUrl = buildJsBundleUrl (requestUrl );
102+ String bundleUrl = buildJsBundleUrl (requestUrl );
94103 String injectedHtml = html
95- .replace ("__JS_BUNDLE_URL__" , cdnUrl )
96- .replace ("__CONFIGURATION__" , """
97- {
98- url: "%s"
99- }
100- """ .formatted (buildApiDocsUrl (requestUrl )));
104+ .replace ("__JS_BUNDLE_URL__" , bundleUrl )
105+ .replace ("__CONFIGURATION__" , buildConfigurationJson (buildApiDocsUrl (requestUrl )));
101106
102107 return ResponseEntity .ok ()
103108 .contentType (MediaType .TEXT_HTML )
@@ -163,7 +168,7 @@ protected String buildJsBundleUrl(String requestUrl, String scalarPath) {
163168 if (SCALAR_DEFAULT_URL .equals (originalScalarUrl )) {
164169 int firstPathSlash = requestUrl .indexOf ('/' , requestUrl .indexOf ("://" ) + 3 );
165170 String path = firstPathSlash >= 0 ? requestUrl .substring (firstPathSlash ) : "/" ;
166- if ( path .endsWith ("/" ))
171+ if ( path .endsWith ("/" ))
167172 path = path .substring (0 , path .length () - 1 );
168173 return path + DEFAULT_PATH_SEPARATOR + SCALAR_JS_FILENAME ;
169174 }
@@ -187,158 +192,20 @@ protected String buildJsBundleUrl(String requestUrl, String scalarPath) {
187192 protected abstract String buildJsBundleUrl (String requestUrl );
188193
189194 /**
190- * Builds the configuration JSON for the Scalar API Reference .
195+ * Build configuration json string .
191196 *
192- * @return the configuration JSON as a string
197+ * @param requestUrl the request url
198+ * @return the string
193199 */
194- private String buildConfigurationJson () {
195- StringBuilder config = new StringBuilder ();
196- config .append ("{" );
197-
198- // Add URL
199- config .append ("\n url: \" " ).append (escapeJson (scalarProperties .getUrl ())).append ("\" " );
200-
201- // Add sources
202- if (scalarProperties .getSources () != null && !scalarProperties .getSources ().isEmpty ()) {
203- config .append (",\n sources: " ).append (buildSourcesJsonArray (scalarProperties .getSources ()));
204- }
205-
206- // Add showSidebar
207- if (!scalarProperties .isShowSidebar ()) {
208- config .append (",\n showSidebar: false" );
209- }
210-
211- // Add hideModels
212- if (scalarProperties .isHideModels ()) {
213- config .append (",\n hideModels: true" );
214- }
215-
216- // Add hideTestRequestButton
217- if (scalarProperties .isHideTestRequestButton ()) {
218- config .append (",\n hideTestRequestButton: true" );
219- }
220-
221- // Add darkMode
222- if (scalarProperties .isDarkMode ()) {
223- config .append (",\n darkMode: true" );
224- }
225-
226- // Add hideDarkModeToggle
227- if (scalarProperties .isHideDarkModeToggle ()) {
228- config .append (",\n hideDarkModeToggle: true" );
229- }
230-
231- // Add customCss
232- if (scalarProperties .getCustomCss () != null && !scalarProperties .getCustomCss ().trim ().isEmpty ()) {
233- config .append (",\n customCss: \" " ).append (escapeJson (scalarProperties .getCustomCss ())).append ("\" " );
200+ private String buildConfigurationJson (String requestUrl ) {
201+ try {
202+ this .scalarProperties .setUrl (requestUrl );
203+ ScalarConfiguration config = ScalarConfigurationMapper .map (scalarProperties );
204+ return objectMapper .writeValueAsString (config );
234205 }
235-
236- // Add theme
237- if (scalarProperties .getTheme () != null && !"default" .equals (scalarProperties .getTheme ())) {
238- config .append (",\n theme: \" " ).append (escapeJson (scalarProperties .getTheme ())).append ("\" " );
206+ catch (JsonProcessingException e ) {
207+ throw new RuntimeException ("Failed to serialize Scalar configuration" , e );
239208 }
240-
241- // Add layout
242- if (scalarProperties .getLayout () != null && !"modern" .equals (scalarProperties .getLayout ())) {
243- config .append (",\n layout: \" " ).append (escapeJson (scalarProperties .getLayout ())).append ("\" " );
244- }
245-
246- // Add hideSearch
247- if (scalarProperties .isHideSearch ()) {
248- config .append (",\n hideSearch: true" );
249- }
250-
251- // Add documentDownloadType
252- if (scalarProperties .getDocumentDownloadType () != null && !"both" .equals (scalarProperties .getDocumentDownloadType ())) {
253- config .append (",\n documentDownloadType: \" " ).append (escapeJson (scalarProperties .getDocumentDownloadType ())).append ("\" " );
254- }
255-
256- config .append ("\n }" );
257- return config .toString ();
258209 }
259210
260- /**
261- * Escapes a string for JSON output.
262- *
263- * @param input the input string
264- * @return the escaped string
265- */
266- private String escapeJson (String input ) {
267- if (input == null ) {
268- return "" ;
269- }
270- return input .replace ("\\ " , "\\ \\ " )
271- .replace ("\" " , "\\ \" " )
272- .replace ("\n " , "\\ n" )
273- .replace ("\r " , "\\ r" )
274- .replace ("\t " , "\\ t" );
275- }
276-
277- /**
278- * Builds the JSON for the OpenAPI reference sources
279- *
280- * @param sources list of OpenAPI reference sources
281- * @return the sources as a JSON string
282- */
283- private String buildSourcesJsonArray (List <ScalarSource > sources ) {
284- final StringBuilder builder = new StringBuilder ("[" );
285-
286- // Filter out sources with invalid urls
287- final List <ScalarProperties .ScalarSource > filteredSources = sources .stream ()
288- .filter (source -> isNotNullOrBlank (source .getUrl ()))
289- .collect (Collectors .toList ());
290-
291- // Append each source to json array
292- for (int i = 0 ; i < filteredSources .size (); i ++) {
293- final ScalarProperties .ScalarSource source = filteredSources .get (i );
294-
295- final String sourceJson = buildSourceJson (source );
296- builder .append ("\n " ).append (sourceJson );
297-
298- if (i != filteredSources .size () - 1 ) {
299- builder .append ("," );
300- }
301- }
302-
303- builder .append ("\n ]" );
304- return builder .toString ();
305- }
306-
307- /**
308- * Builds the JSON for an OpenAPI reference source
309- *
310- * @param source the OpenAPI reference source
311- * @return the source as a JSON string
312- */
313- private String buildSourceJson (ScalarProperties .ScalarSource source ) {
314- final StringBuilder builder = new StringBuilder ("{" );
315-
316- builder .append ("\n url: \" " ).append (escapeJson (source .getUrl ())).append ("\" " );
317-
318-
319- if (isNotNullOrBlank (source .getTitle ())) {
320- builder .append (",\n title: \" " ).append (escapeJson (source .getTitle ())).append ("\" " );
321- }
322-
323- if (isNotNullOrBlank (source .getSlug ())) {
324- builder .append (",\n slug: \" " ).append (escapeJson (source .getSlug ())).append ("\" " );
325- }
326-
327- if (source .isDefault () != null ) {
328- builder .append (",\n default: " ).append (source .isDefault ());
329- }
330-
331- builder .append ("\n }" );
332- return builder .toString ();
333- }
334-
335- /**
336- * Returns whether a String is not null or blank
337- *
338- * @param input the string
339- * @return whether the string is not null or blank
340- */
341- private boolean isNotNullOrBlank (String input ) {
342- return input != null && !input .isBlank ();
343- }
344211}
0 commit comments