@@ -58,6 +58,7 @@ struct AutoRouterInput {
5858 title : Option < LitStr > ,
5959 version : Option < LitStr > ,
6060 docs_url : Option < LitStr > ,
61+ redoc_url : Option < LitStr > ,
6162}
6263
6364impl Parse for AutoRouterInput {
@@ -67,6 +68,7 @@ impl Parse for AutoRouterInput {
6768 let mut title = None ;
6869 let mut version = None ;
6970 let mut docs_url = None ;
71+ let mut redoc_url = None ;
7072
7173 while !input. is_empty ( ) {
7274 let lookahead = input. lookahead1 ( ) ;
@@ -88,6 +90,10 @@ impl Parse for AutoRouterInput {
8890 input. parse :: < syn:: Token ![ =] > ( ) ?;
8991 docs_url = Some ( input. parse ( ) ?) ;
9092 }
93+ "redoc_url" => {
94+ input. parse :: < syn:: Token ![ =] > ( ) ?;
95+ redoc_url = Some ( input. parse ( ) ?) ;
96+ }
9197 "title" => {
9298 input. parse :: < syn:: Token ![ =] > ( ) ?;
9399 title = Some ( input. parse ( ) ?) ;
@@ -146,6 +152,11 @@ impl Parse for AutoRouterInput {
146152 . map ( |f| LitStr :: new ( & f, Span :: call_site ( ) ) )
147153 . ok ( )
148154 } ) ,
155+ redoc_url : redoc_url. or_else ( || {
156+ std:: env:: var ( "VESPERA_REDOC_URL" )
157+ . map ( |f| LitStr :: new ( & f, Span :: call_site ( ) ) )
158+ . ok ( )
159+ } ) ,
149160 } )
150161 }
151162}
@@ -164,6 +175,7 @@ pub fn vespera(input: TokenStream) -> TokenStream {
164175 let title = input. title . map ( |t| t. value ( ) ) ;
165176 let version = input. version . map ( |v| v. value ( ) ) ;
166177 let docs_url = input. docs_url . map ( |u| u. value ( ) ) ;
178+ let redoc_url = input. redoc_url . map ( |u| u. value ( ) ) ;
167179
168180 let folder_path = find_folder_path ( & folder_name) ;
169181
@@ -192,8 +204,9 @@ pub fn vespera(input: TokenStream) -> TokenStream {
192204 metadata. structs . extend ( schemas) ;
193205
194206 let mut docs_info = None ;
207+ let mut redoc_info = None ;
195208
196- if openapi_file_name. is_some ( ) || docs_url. is_some ( ) {
209+ if openapi_file_name. is_some ( ) || docs_url. is_some ( ) || redoc_url . is_some ( ) {
197210 // Generate OpenAPI document using collected metadata
198211
199212 // Serialize to JSON
@@ -214,11 +227,14 @@ pub fn vespera(input: TokenStream) -> TokenStream {
214227 std:: fs:: write ( openapi_file_name, & json_str) . unwrap ( ) ;
215228 }
216229 if let Some ( docs_url) = docs_url {
217- docs_info = Some ( ( docs_url, json_str) ) ;
230+ docs_info = Some ( ( docs_url, json_str. clone ( ) ) ) ;
231+ }
232+ if let Some ( redoc_url) = redoc_url {
233+ redoc_info = Some ( ( redoc_url, json_str) ) ;
218234 }
219235 }
220236
221- generate_router_code ( & metadata, docs_info) . into ( )
237+ generate_router_code ( & metadata, docs_info, redoc_info ) . into ( )
222238}
223239
224240fn find_folder_path ( folder_name : & str ) -> std:: path:: PathBuf {
@@ -235,6 +251,7 @@ fn find_folder_path(folder_name: &str) -> std::path::PathBuf {
235251fn generate_router_code (
236252 metadata : & CollectedMetadata ,
237253 docs_info : Option < ( String , String ) > ,
254+ redoc_info : Option < ( String , String ) > ,
238255) -> proc_macro2:: TokenStream {
239256 let mut router_nests = Vec :: new ( ) ;
240257
@@ -318,6 +335,44 @@ fn generate_router_code(
318335 ) ) ;
319336 }
320337
338+ if let Some ( ( redoc_url, spec) ) = redoc_info {
339+ let method_path = http_method_to_token_stream ( HttpMethod :: Get ) ;
340+
341+ let html = format ! (
342+ r#"
343+ <!DOCTYPE html>
344+ <html lang="en">
345+ <head>
346+ <meta charset="UTF-8">
347+ <title>ReDoc</title>
348+ <meta name="viewport" content="width=device-width, initial-scale=1">
349+ <style>
350+ body {{
351+ margin: 0;
352+ padding: 0;
353+ }}
354+ </style>
355+ <link rel="stylesheet" href="https://unpkg.com/redoc/bundles/redoc.standalone.css" />
356+ </head>
357+ <body>
358+ <div id="redoc-container"></div>
359+ <script src="https://unpkg.com/redoc/bundles/redoc.standalone.js"></script>
360+ <script>
361+ const openapiSpec = {spec_json};
362+ Redoc.init(openapiSpec, {{}}, document.getElementById("redoc-container"));
363+ </script>
364+ </body>
365+ </html>
366+ "# ,
367+ spec_json = spec
368+ )
369+ . replace ( "\n " , "" ) ;
370+
371+ router_nests. push ( quote ! (
372+ . route( #redoc_url, #method_path( || async { vespera:: axum:: response:: Html ( #html) } ) )
373+ ) ) ;
374+ }
375+
321376 quote ! {
322377 vespera:: axum:: Router :: new( )
323378 #( #router_nests ) *
@@ -348,6 +403,7 @@ mod tests {
348403 let result = generate_router_code (
349404 & collect_metadata ( & temp_dir. path ( ) , folder_name) . unwrap ( ) ,
350405 None ,
406+ None ,
351407 ) ;
352408 let code = result. to_string ( ) ;
353409
@@ -504,6 +560,7 @@ pub fn get_users() -> String {
504560 let result = generate_router_code (
505561 & collect_metadata ( & temp_dir. path ( ) , folder_name) . unwrap ( ) ,
506562 None ,
563+ None ,
507564 ) ;
508565 let code = result. to_string ( ) ;
509566
@@ -588,6 +645,7 @@ pub fn update_user() -> String {
588645 let result = generate_router_code (
589646 & collect_metadata ( & temp_dir. path ( ) , folder_name) . unwrap ( ) ,
590647 None ,
648+ None ,
591649 ) ;
592650 let code = result. to_string ( ) ;
593651
@@ -641,6 +699,7 @@ pub fn create_users() -> String {
641699 let result = generate_router_code (
642700 & collect_metadata ( & temp_dir. path ( ) , folder_name) . unwrap ( ) ,
643701 None ,
702+ None ,
644703 ) ;
645704 let code = result. to_string ( ) ;
646705
@@ -686,6 +745,7 @@ pub fn index() -> String {
686745 let result = generate_router_code (
687746 & collect_metadata ( & temp_dir. path ( ) , folder_name) . unwrap ( ) ,
688747 None ,
748+ None ,
689749 ) ;
690750 let code = result. to_string ( ) ;
691751
@@ -721,6 +781,7 @@ pub fn get_users() -> String {
721781 let result = generate_router_code (
722782 & collect_metadata ( & temp_dir. path ( ) , folder_name) . unwrap ( ) ,
723783 None ,
784+ None ,
724785 ) ;
725786 let code = result. to_string ( ) ;
726787
0 commit comments