@@ -13,7 +13,9 @@ use quote::quote;
1313use std:: path:: Path ;
1414use std:: sync:: { LazyLock , Mutex } ;
1515use syn:: LitStr ;
16+ use syn:: bracketed;
1617use syn:: parse:: { Parse , ParseStream } ;
18+ use syn:: punctuated:: Punctuated ;
1719
1820use crate :: collector:: collect_metadata;
1921use crate :: metadata:: { CollectedMetadata , StructMetadata } ;
@@ -57,7 +59,7 @@ pub fn derive_schema(input: TokenStream) -> TokenStream {
5759
5860struct AutoRouterInput {
5961 dir : Option < LitStr > ,
60- openapi : Option < LitStr > ,
62+ openapi : Option < Vec < LitStr > > ,
6163 title : Option < LitStr > ,
6264 version : Option < LitStr > ,
6365 docs_url : Option < LitStr > ,
@@ -86,8 +88,7 @@ impl Parse for AutoRouterInput {
8688 dir = Some ( input. parse ( ) ?) ;
8789 }
8890 "openapi" => {
89- input. parse :: < syn:: Token ![ =] > ( ) ?;
90- openapi = Some ( input. parse ( ) ?) ;
91+ openapi = Some ( parse_openapi_values ( input) ?) ;
9192 }
9293 "docs_url" => {
9394 input. parse :: < syn:: Token ![ =] > ( ) ?;
@@ -137,7 +138,7 @@ impl Parse for AutoRouterInput {
137138 } ) ,
138139 openapi : openapi. or_else ( || {
139140 std:: env:: var ( "VESPERA_OPENAPI" )
140- . map ( |f| LitStr :: new ( & f, Span :: call_site ( ) ) )
141+ . map ( |f| vec ! [ LitStr :: new( & f, Span :: call_site( ) ) ] )
141142 . ok ( )
142143 } ) ,
143144 title : title. or_else ( || {
@@ -164,6 +165,21 @@ impl Parse for AutoRouterInput {
164165 }
165166}
166167
168+ fn parse_openapi_values ( input : ParseStream ) -> syn:: Result < Vec < LitStr > > {
169+ input. parse :: < syn:: Token ![ =] > ( ) ?;
170+
171+ if input. peek ( syn:: token:: Bracket ) {
172+ let content;
173+ let _ = bracketed ! ( content in input) ;
174+ let entries: Punctuated < LitStr , syn:: Token ![ , ] > =
175+ content. parse_terminated ( |input| input. parse :: < LitStr > ( ) , syn:: Token ![ , ] ) ?;
176+ Ok ( entries. into_iter ( ) . collect ( ) )
177+ } else {
178+ let single: LitStr = input. parse ( ) ?;
179+ Ok ( vec ! [ single] )
180+ }
181+ }
182+
167183#[ proc_macro]
168184pub fn vespera ( input : TokenStream ) -> TokenStream {
169185 let input = syn:: parse_macro_input!( input as AutoRouterInput ) ;
@@ -173,7 +189,12 @@ pub fn vespera(input: TokenStream) -> TokenStream {
173189 . map ( |f| f. value ( ) )
174190 . unwrap_or_else ( || "routes" . to_string ( ) ) ;
175191
176- let openapi_file_name = input. openapi . map ( |f| f. value ( ) ) ;
192+ let openapi_file_names = input
193+ . openapi
194+ . unwrap_or_default ( )
195+ . into_iter ( )
196+ . map ( |f| f. value ( ) )
197+ . collect :: < Vec < _ > > ( ) ;
177198
178199 let title = input. title . map ( |t| t. value ( ) ) ;
179200 let version = input. version . map ( |v| v. value ( ) ) ;
@@ -209,7 +230,7 @@ pub fn vespera(input: TokenStream) -> TokenStream {
209230 let mut docs_info = None ;
210231 let mut redoc_info = None ;
211232
212- if openapi_file_name . is_some ( ) || docs_url. is_some ( ) || redoc_url. is_some ( ) {
233+ if !openapi_file_names . is_empty ( ) || docs_url. is_some ( ) || redoc_url. is_some ( ) {
213234 // Generate OpenAPI document using collected metadata
214235
215236 // Serialize to JSON
@@ -226,8 +247,18 @@ pub fn vespera(input: TokenStream) -> TokenStream {
226247 . into ( ) ;
227248 }
228249 } ;
229- if let Some ( openapi_file_name) = openapi_file_name {
230- std:: fs:: write ( openapi_file_name, & json_str) . unwrap ( ) ;
250+ for openapi_file_name in & openapi_file_names {
251+ if let Err ( e) = std:: fs:: write ( openapi_file_name, & json_str) {
252+ return syn:: Error :: new (
253+ Span :: call_site ( ) ,
254+ format ! (
255+ "Failed to write OpenAPI document to {}: {}" ,
256+ openapi_file_name, e
257+ ) ,
258+ )
259+ . to_compile_error ( )
260+ . into ( ) ;
261+ }
231262 }
232263 if let Some ( docs_url) = docs_url {
233264 docs_info = Some ( ( docs_url, json_str. clone ( ) ) ) ;
0 commit comments