2828
2929import java .io .IOException ;
3030import java .io .InputStream ;
31- import java .net .URLDecoder ;
3231import java .nio .charset .StandardCharsets ;
33- import java .util .List ;
34- import java .util .stream .Collectors ;
3532
3633import com .scalar .maven .webjar .ScalarProperties ;
37- import com .scalar .maven .webjar .ScalarProperties .ScalarSource ;
34+ import com .scalar .maven .webjar .internal .ScalarConfiguration ;
35+ import com .scalar .maven .webjar .internal .ScalarConfigurationMapper ;
36+ import tools .jackson .core .JacksonException ;
37+ import tools .jackson .databind .ObjectMapper ;
3838
3939import org .springframework .http .MediaType ;
4040import org .springframework .http .ResponseEntity ;
4141
42+ import static org .springdoc .scalar .ScalarConstants .HTML_TEMPLATE_PATH ;
4243import static org .springdoc .scalar .ScalarConstants .SCALAR_DEFAULT_URL ;
4344import static org .springdoc .scalar .ScalarConstants .SCALAR_JS_FILENAME ;
4445import static org .springframework .util .AntPathMatcher .DEFAULT_PATH_SEPARATOR ;
@@ -60,14 +61,21 @@ public abstract class AbstractScalarController {
6061 */
6162 protected final String originalScalarUrl ;
6263
64+ /**
65+ * The Object mapper.
66+ */
67+ private final ObjectMapper objectMapper ;
68+
6369 /**
6470 * Instantiates a new Abstract scalar controller.
6571 *
6672 * @param scalarProperties the scalar properties
73+ * @param objectMapper the object mapper
6774 */
68- protected AbstractScalarController (ScalarProperties scalarProperties ) {
75+ protected AbstractScalarController (ScalarProperties scalarProperties , ObjectMapper objectMapper ) {
6976 this .scalarProperties = scalarProperties ;
7077 this .originalScalarUrl = scalarProperties .getUrl ();
78+ this .objectMapper = objectMapper ;
7179 }
7280
7381 /**
@@ -82,22 +90,18 @@ protected AbstractScalarController(ScalarProperties scalarProperties) {
8290 */
8391 protected ResponseEntity <String > getDocs (String requestUrl ) throws IOException {
8492 // Load the template HTML
85- InputStream inputStream = getClass ().getResourceAsStream ("/META-INF/resources/webjars/scalar/index.html" );
93+ InputStream inputStream = getClass ().getResourceAsStream (HTML_TEMPLATE_PATH );
8694 if (inputStream == null ) {
87- return ResponseEntity . notFound (). build ( );
95+ throw new IOException ( "HTML template not found at: " + HTML_TEMPLATE_PATH );
8896 }
8997
9098 String html = new String (inputStream .readAllBytes (), StandardCharsets .UTF_8 );
91- requestUrl = decode ( requestUrl );
99+
92100 // Replace the placeholders with actual values
93- String cdnUrl = buildJsBundleUrl (requestUrl );
101+ String bundleUrl = buildJsBundleUrl (requestUrl );
94102 String injectedHtml = html
95- .replace ("__JS_BUNDLE_URL__" , cdnUrl )
96- .replace ("__CONFIGURATION__" , """
97- {
98- url: "%s"
99- }
100- """ .formatted (buildApiDocsUrl (requestUrl )));
103+ .replace ("__JS_BUNDLE_URL__" , bundleUrl )
104+ .replace ("__CONFIGURATION__" , buildConfigurationJson (buildApiDocsUrl (requestUrl )));
101105
102106 return ResponseEntity .ok ()
103107 .contentType (MediaType .TEXT_HTML )
@@ -126,16 +130,6 @@ protected ResponseEntity<byte[]> getScalarJs() throws IOException {
126130 .body (jsContent );
127131 }
128132
129- /**
130- * Decode string.
131- *
132- * @param requestURI the request uri
133- * @return the string
134- */
135- protected String decode (String requestURI ) {
136- return URLDecoder .decode (requestURI , StandardCharsets .UTF_8 );
137- }
138-
139133 /**
140134 * Gets api docs url.
141135 *
@@ -186,159 +180,20 @@ protected String buildJsBundleUrl(String requestUrl, String scalarPath) {
186180 */
187181 protected abstract String buildJsBundleUrl (String requestUrl );
188182
189- /**
190- * Builds the configuration JSON for the Scalar API Reference.
191- *
192- * @return the configuration JSON as a string
193- */
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 ("\" " );
234- }
235-
236- // Add theme
237- if (scalarProperties .getTheme () != null && !"default" .equals (scalarProperties .getTheme ())) {
238- config .append (",\n theme: \" " ).append (escapeJson (scalarProperties .getTheme ())).append ("\" " );
239- }
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 ();
258- }
259-
260- /**
261- * Escapes a string for JSON output.
183+ /**
184+ * Build configuration json string.
262185 *
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 <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 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
186+ * @param requestUrl the request url
187+ * @return the string
312188 */
313- private String buildSourceJson (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 ("\" " );
189+ private String buildConfigurationJson (String requestUrl ) {
190+ try {
191+ this .scalarProperties .setUrl (requestUrl );
192+ ScalarConfiguration config = ScalarConfigurationMapper .map (scalarProperties );
193+ return objectMapper .writeValueAsString (config );
321194 }
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 ());
195+ catch (JacksonException e ) {
196+ throw new RuntimeException ("Failed to serialize Scalar configuration" , e );
329197 }
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 ();
343198 }
344199}
0 commit comments