@@ -57,6 +57,7 @@ struct AutoRouterInput {
5757 openapi : Option < LitStr > ,
5858 title : Option < LitStr > ,
5959 version : Option < LitStr > ,
60+ docs_url : Option < LitStr > ,
6061}
6162
6263impl Parse for AutoRouterInput {
@@ -65,6 +66,7 @@ impl Parse for AutoRouterInput {
6566 let mut openapi = None ;
6667 let mut title = None ;
6768 let mut version = None ;
69+ let mut docs_url = None ;
6870
6971 while !input. is_empty ( ) {
7072 let lookahead = input. lookahead1 ( ) ;
@@ -82,6 +84,10 @@ impl Parse for AutoRouterInput {
8284 input. parse :: < syn:: Token ![ =] > ( ) ?;
8385 openapi = Some ( input. parse ( ) ?) ;
8486 }
87+ "docs_url" => {
88+ input. parse :: < syn:: Token ![ =] > ( ) ?;
89+ docs_url = Some ( input. parse ( ) ?) ;
90+ }
8591 "title" => {
8692 input. parse :: < syn:: Token ![ =] > ( ) ?;
8793 title = Some ( input. parse ( ) ?) ;
@@ -119,6 +125,7 @@ impl Parse for AutoRouterInput {
119125 openapi,
120126 title,
121127 version,
128+ docs_url,
122129 } )
123130 }
124131}
@@ -136,6 +143,7 @@ pub fn vespera(input: TokenStream) -> TokenStream {
136143
137144 let title = input. title . map ( |t| t. value ( ) ) ;
138145 let version = input. version . map ( |v| v. value ( ) ) ;
146+ let docs_url = input. docs_url . map ( |u| u. value ( ) ) ;
139147
140148 let folder_path = find_folder_path ( & folder_name) ;
141149
@@ -163,12 +171,15 @@ pub fn vespera(input: TokenStream) -> TokenStream {
163171
164172 metadata. structs . extend ( schemas) ;
165173
166- if let Some ( openapi_file_name) = openapi_file_name {
174+ let mut docs_info = None ;
175+
176+ if openapi_file_name. is_some ( ) || docs_url. is_some ( ) {
167177 // Generate OpenAPI document using collected metadata
168- let openapi_doc = generate_openapi_doc_with_metadata ( title, version, & metadata) ;
169178
170179 // Serialize to JSON
171- let json_str = match serde_json:: to_string_pretty ( & openapi_doc) {
180+ let json_str = match serde_json:: to_string_pretty ( & generate_openapi_doc_with_metadata (
181+ title, version, & metadata,
182+ ) ) {
172183 Ok ( json) => json,
173184 Err ( e) => {
174185 return syn:: Error :: new (
@@ -179,10 +190,15 @@ pub fn vespera(input: TokenStream) -> TokenStream {
179190 . into ( ) ;
180191 }
181192 } ;
182- std:: fs:: write ( openapi_file_name, json_str) . unwrap ( ) ;
193+ if let Some ( openapi_file_name) = openapi_file_name {
194+ std:: fs:: write ( openapi_file_name, & json_str) . unwrap ( ) ;
195+ }
196+ if let Some ( docs_url) = docs_url {
197+ docs_info = Some ( ( docs_url, json_str) ) ;
198+ }
183199 }
184200
185- generate_router_code ( & metadata) . into ( )
201+ generate_router_code ( & metadata, docs_info ) . into ( )
186202}
187203
188204fn find_folder_path ( folder_name : & str ) -> std:: path:: PathBuf {
@@ -196,7 +212,10 @@ fn find_folder_path(folder_name: &str) -> std::path::PathBuf {
196212 Path :: new ( folder_name) . to_path_buf ( )
197213}
198214
199- fn generate_router_code ( metadata : & CollectedMetadata ) -> proc_macro2:: TokenStream {
215+ fn generate_router_code (
216+ metadata : & CollectedMetadata ,
217+ docs_info : Option < ( String , String ) > ,
218+ ) -> proc_macro2:: TokenStream {
200219 let mut router_nests = Vec :: new ( ) ;
201220
202221 for route in & metadata. routes {
@@ -233,6 +252,52 @@ fn generate_router_code(metadata: &CollectedMetadata) -> proc_macro2::TokenStrea
233252 ) ) ;
234253 }
235254
255+ if let Some ( ( docs_url, spec) ) = docs_info {
256+ let method_path = http_method_to_token_stream ( HttpMethod :: Get ) ;
257+
258+ let html = format ! (
259+ r#"
260+ <!DOCTYPE html>
261+ <html lang="en">
262+ <head>
263+ <meta charset="UTF-8">
264+ <title>Swagger UI</title>
265+ <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist/swagger-ui.css" />
266+ </head>
267+ <body style="margin: 0; padding: 0;">
268+ <div id="swagger-ui"></div>
269+
270+ <script src="https://unpkg.com/swagger-ui-dist/swagger-ui-bundle.js"></script>
271+ <script src="https://unpkg.com/swagger-ui-dist/swagger-ui-standalone-preset.js"></script>
272+
273+ <script>
274+ const openapiSpec = {spec_json};
275+
276+ window.onload = () => {{
277+ SwaggerUIBundle({{
278+ spec: openapiSpec,
279+ dom_id: "\#swagger-ui",
280+ presets: [
281+ SwaggerUIBundle.presets.apis,
282+ SwaggerUIStandalonePreset
283+ ],
284+ layout: "StandaloneLayout"
285+ }});
286+ }};
287+ </script>
288+
289+ </body>
290+ </html>
291+ "# ,
292+ spec_json = spec
293+ )
294+ . replace ( "\n " , "" ) ;
295+
296+ router_nests. push ( quote ! (
297+ . route( #docs_url, #method_path( || async { vespera:: axum:: response:: Html ( #html) } ) )
298+ ) ) ;
299+ }
300+
236301 quote ! {
237302 vespera:: axum:: Router :: new( )
238303 #( #router_nests ) *
@@ -260,8 +325,10 @@ mod tests {
260325 let temp_dir = TempDir :: new ( ) . expect ( "Failed to create temp dir" ) ;
261326 let folder_name = "routes" ;
262327
263- let result =
264- generate_router_code ( & collect_metadata ( & temp_dir. path ( ) , folder_name) . unwrap ( ) ) ;
328+ let result = generate_router_code (
329+ & collect_metadata ( & temp_dir. path ( ) , folder_name) . unwrap ( ) ,
330+ None ,
331+ ) ;
265332 let code = result. to_string ( ) ;
266333
267334 // Should generate empty router
@@ -414,8 +481,10 @@ pub fn get_users() -> String {
414481 create_temp_file ( & temp_dir, filename, content) ;
415482 }
416483
417- let result =
418- generate_router_code ( & collect_metadata ( & temp_dir. path ( ) , folder_name) . unwrap ( ) ) ;
484+ let result = generate_router_code (
485+ & collect_metadata ( & temp_dir. path ( ) , folder_name) . unwrap ( ) ,
486+ None ,
487+ ) ;
419488 let code = result. to_string ( ) ;
420489
421490 // Check router initialization (quote! generates "vespera :: axum :: Router :: new ()")
@@ -496,8 +565,10 @@ pub fn update_user() -> String {
496565"# ,
497566 ) ;
498567
499- let result =
500- generate_router_code ( & collect_metadata ( & temp_dir. path ( ) , folder_name) . unwrap ( ) ) ;
568+ let result = generate_router_code (
569+ & collect_metadata ( & temp_dir. path ( ) , folder_name) . unwrap ( ) ,
570+ None ,
571+ ) ;
501572 let code = result. to_string ( ) ;
502573
503574 // Check router initialization (quote! generates "vespera :: axum :: Router :: new ()")
@@ -547,8 +618,10 @@ pub fn create_users() -> String {
547618"# ,
548619 ) ;
549620
550- let result =
551- generate_router_code ( & collect_metadata ( & temp_dir. path ( ) , folder_name) . unwrap ( ) ) ;
621+ let result = generate_router_code (
622+ & collect_metadata ( & temp_dir. path ( ) , folder_name) . unwrap ( ) ,
623+ None ,
624+ ) ;
552625 let code = result. to_string ( ) ;
553626
554627 // Check router initialization (quote! generates "vespera :: axum :: Router :: new ()")
@@ -590,8 +663,10 @@ pub fn index() -> String {
590663"# ,
591664 ) ;
592665
593- let result =
594- generate_router_code ( & collect_metadata ( & temp_dir. path ( ) , folder_name) . unwrap ( ) ) ;
666+ let result = generate_router_code (
667+ & collect_metadata ( & temp_dir. path ( ) , folder_name) . unwrap ( ) ,
668+ None ,
669+ ) ;
595670 let code = result. to_string ( ) ;
596671
597672 // Check router initialization (quote! generates "vespera :: axum :: Router :: new ()")
@@ -623,8 +698,10 @@ pub fn get_users() -> String {
623698"# ,
624699 ) ;
625700
626- let result =
627- generate_router_code ( & collect_metadata ( & temp_dir. path ( ) , folder_name) . unwrap ( ) ) ;
701+ let result = generate_router_code (
702+ & collect_metadata ( & temp_dir. path ( ) , folder_name) . unwrap ( ) ,
703+ None ,
704+ ) ;
628705 let code = result. to_string ( ) ;
629706
630707 // Check router initialization (quote! generates "vespera :: axum :: Router :: new ()")
0 commit comments