Skip to content

Commit a4a1459

Browse files
committed
Fix openapi
1 parent 23cf0fd commit a4a1459

6 files changed

Lines changed: 115 additions & 85 deletions

File tree

crates/vespera_macro/src/lib.rs

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,15 @@ pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
7979
///
8080
/// Supports `#[schema(name = "CustomName")]` attribute to set custom `OpenAPI` schema name.
8181
#[cfg(not(tarpaulin_include))]
82-
#[allow(clippy::missing_panics_doc)]
8382
#[proc_macro_derive(Schema, attributes(schema, serde))]
8483
pub fn derive_schema(input: TokenStream) -> TokenStream {
8584
let input = syn::parse_macro_input!(input as syn::DeriveInput);
8685
let (metadata, expanded) = schema_impl::process_derive_schema(&input);
8786
let name = metadata.name.clone();
88-
SCHEMA_STORAGE.lock().unwrap().insert(name, metadata);
87+
SCHEMA_STORAGE
88+
.lock()
89+
.unwrap_or_else(std::sync::PoisonError::into_inner)
90+
.insert(name, metadata);
8991
TokenStream::from(expanded)
9092
}
9193

@@ -135,13 +137,14 @@ pub fn derive_schema(input: TokenStream) -> TokenStream {
135137
/// let list_schema = schema!(User, pick = ["id", "name"]);
136138
/// ```
137139
#[cfg(not(tarpaulin_include))]
138-
#[allow(clippy::missing_panics_doc)]
139140
#[proc_macro]
140141
pub fn schema(input: TokenStream) -> TokenStream {
141142
let input = syn::parse_macro_input!(input as schema_macro::SchemaInput);
142143

143144
// Get stored schemas
144-
let storage = SCHEMA_STORAGE.lock().unwrap();
145+
let storage = SCHEMA_STORAGE
146+
.lock()
147+
.unwrap_or_else(std::sync::PoisonError::into_inner);
145148

146149
match schema_macro::generate_schema_code(&input, &storage) {
147150
Ok(tokens) => TokenStream::from(tokens),
@@ -204,14 +207,15 @@ pub fn schema(input: TokenStream) -> TokenStream {
204207
/// }
205208
/// ```
206209
#[cfg(not(tarpaulin_include))]
207-
#[allow(clippy::missing_panics_doc)]
208210
#[proc_macro]
209211
pub fn schema_type(input: TokenStream) -> TokenStream {
210212
let input = syn::parse_macro_input!(input as schema_macro::SchemaTypeInput);
211213

212214
// Get stored schemas and generate code
213215
let (tokens, generated_metadata) = {
214-
let storage = SCHEMA_STORAGE.lock().unwrap();
216+
let storage = SCHEMA_STORAGE
217+
.lock()
218+
.unwrap_or_else(std::sync::PoisonError::into_inner);
215219
match schema_macro::generate_schema_type_code(&input, &storage) {
216220
Ok(result) => result,
217221
Err(e) => return e.to_compile_error().into(),
@@ -222,18 +226,22 @@ pub fn schema_type(input: TokenStream) -> TokenStream {
222226
// This ensures it appears in OpenAPI even when `ignore` is set
223227
if let Some(metadata) = generated_metadata {
224228
let name = metadata.name.clone();
225-
SCHEMA_STORAGE.lock().unwrap().insert(name, metadata);
229+
SCHEMA_STORAGE
230+
.lock()
231+
.unwrap_or_else(std::sync::PoisonError::into_inner)
232+
.insert(name, metadata);
226233
}
227234
TokenStream::from(tokens)
228235
}
229236

230237
#[cfg(not(tarpaulin_include))]
231-
#[allow(clippy::missing_panics_doc)]
232238
#[proc_macro]
233239
pub fn vespera(input: TokenStream) -> TokenStream {
234240
let input = syn::parse_macro_input!(input as AutoRouterInput);
235241
let processed = process_vespera_input(input);
236-
let schema_storage = SCHEMA_STORAGE.lock().unwrap();
242+
let schema_storage = SCHEMA_STORAGE
243+
.lock()
244+
.unwrap_or_else(std::sync::PoisonError::into_inner);
237245

238246
match process_vespera_macro(&processed, &schema_storage) {
239247
Ok(tokens) => tokens.into(),
@@ -264,16 +272,24 @@ pub fn vespera(input: TokenStream) -> TokenStream {
264272
/// ```
265273
///
266274
#[cfg(not(tarpaulin_include))]
267-
#[allow(clippy::missing_panics_doc)]
268275
#[proc_macro]
269276
pub fn export_app(input: TokenStream) -> TokenStream {
270277
let ExportAppInput { name, dir } = syn::parse_macro_input!(input as ExportAppInput);
271278
let folder_name = dir
272279
.map(|d| d.value())
273280
.or_else(|| std::env::var("VESPERA_DIR").ok())
274281
.unwrap_or_else(|| "routes".to_string());
275-
let schema_storage = SCHEMA_STORAGE.lock().unwrap();
276-
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set");
282+
let schema_storage = SCHEMA_STORAGE
283+
.lock()
284+
.unwrap_or_else(std::sync::PoisonError::into_inner);
285+
let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") else {
286+
return syn::Error::new(
287+
proc_macro2::Span::call_site(),
288+
"export_app! macro: CARGO_MANIFEST_DIR is not set. This macro must be used within a cargo build.",
289+
)
290+
.to_compile_error()
291+
.into();
292+
};
277293

278294
match process_export_app(&name, &folder_name, &schema_storage, &manifest_dir) {
279295
Ok(tokens) => tokens.into(),

crates/vespera_macro/src/openapi_generator.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -224,8 +224,13 @@ fn build_path_items(
224224
if let syn::Item::Fn(fn_item) = item
225225
&& fn_item.sig.ident == route_meta.function_name
226226
{
227-
let method = HttpMethod::try_from(route_meta.method.as_str())
228-
.expect("route method must be a valid HTTP method");
227+
let Ok(method) = HttpMethod::try_from(route_meta.method.as_str()) else {
228+
eprintln!(
229+
"vespera: skipping route '{}' — unknown HTTP method '{}'",
230+
route_meta.path, route_meta.method
231+
);
232+
continue;
233+
};
229234

230235
if let Some(tags) = &route_meta.tags {
231236
for tag in tags {

crates/vespera_macro/src/router_codegen.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,10 @@
3636
use proc_macro2::Span;
3737
use quote::quote;
3838
use syn::{
39-
LitStr, bracketed,
39+
bracketed,
4040
parse::{Parse, ParseStream},
4141
punctuated::Punctuated,
42+
LitStr,
4243
};
4344
use vespera_core::{openapi::Server, route::HttpMethod};
4445

@@ -439,8 +440,13 @@ pub fn generate_router_code(
439440
let mut router_nests = Vec::new();
440441

441442
for route in &metadata.routes {
442-
let http_method = HttpMethod::try_from(route.method.as_str())
443-
.expect("route method must be a valid HTTP method");
443+
let Ok(http_method) = HttpMethod::try_from(route.method.as_str()) else {
444+
eprintln!(
445+
"vespera: skipping route '{}' — unknown HTTP method '{}'",
446+
route.path, route.method
447+
);
448+
continue;
449+
};
444450
let method_path = http_method_to_token_stream(http_method);
445451
let path = &route.path;
446452
let module_path = &route.module_path;

crates/vespera_macro/src/vespera_impl.rs

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -102,16 +102,19 @@ pub fn generate_and_write_openapi(
102102
}
103103

104104
/// Find the folder path for route scanning
105-
pub fn find_folder_path(folder_name: &str) -> std::path::PathBuf {
106-
let root = std::env::var("CARGO_MANIFEST_DIR")
107-
.expect("CARGO_MANIFEST_DIR must be set by cargo during compilation");
105+
pub fn find_folder_path(folder_name: &str) -> MacroResult<std::path::PathBuf> {
106+
let root = std::env::var("CARGO_MANIFEST_DIR").map_err(|_| {
107+
err_call_site(
108+
"CARGO_MANIFEST_DIR is not set. vespera macros must be used within a cargo build.",
109+
)
110+
})?;
108111
let path = format!("{root}/src/{folder_name}");
109112
let path = Path::new(&path);
110113
if path.exists() && path.is_dir() {
111-
return path.to_path_buf();
114+
return Ok(path.to_path_buf());
112115
}
113116

114-
Path::new(folder_name).to_path_buf()
117+
Ok(Path::new(folder_name).to_path_buf())
115118
}
116119

117120
/// Find the workspace root's target directory
@@ -152,7 +155,7 @@ pub fn process_vespera_macro(
152155
processed: &ProcessedVesperaInput,
153156
schema_storage: &HashMap<String, StructMetadata>,
154157
) -> syn::Result<proc_macro2::TokenStream> {
155-
let folder_path = find_folder_path(&processed.folder_name);
158+
let folder_path = find_folder_path(&processed.folder_name)?;
156159
if !folder_path.exists() {
157160
return Err(syn::Error::new(
158161
Span::call_site(),
@@ -183,7 +186,7 @@ pub fn process_export_app(
183186
schema_storage: &HashMap<String, StructMetadata>,
184187
manifest_dir: &str,
185188
) -> syn::Result<proc_macro2::TokenStream> {
186-
let folder_path = find_folder_path(folder_name);
189+
let folder_path = find_folder_path(folder_name)?;
187190
if !folder_path.exists() {
188191
return Err(syn::Error::new(
189192
Span::call_site(),
@@ -390,7 +393,7 @@ mod tests {
390393
#[test]
391394
fn test_find_folder_path_nonexistent_returns_path() {
392395
// When the constructed path doesn't exist, it falls back to using folder_name directly
393-
let result = find_folder_path("nonexistent_folder_xyz");
396+
let result = find_folder_path("nonexistent_folder_xyz").unwrap();
394397
// It should return a PathBuf (either from src/nonexistent... or just the folder name)
395398
assert!(result.to_string_lossy().contains("nonexistent_folder_xyz"));
396399
}
@@ -703,7 +706,7 @@ mod tests {
703706
let absolute_path = temp_dir.path().to_string_lossy().to_string();
704707

705708
// When given an absolute path that exists, it should return it
706-
let result = find_folder_path(&absolute_path);
709+
let result = find_folder_path(&absolute_path).unwrap();
707710
// The function tries src/{folder_name} first, then falls back to the folder_name directly
708711
assert!(
709712
result.to_string_lossy().contains(&absolute_path)
@@ -724,7 +727,7 @@ mod tests {
724727
// SAFETY: We're in a single-threaded test context
725728
unsafe { std::env::set_var("CARGO_MANIFEST_DIR", temp_dir.path()) };
726729

727-
let result = find_folder_path("routes");
730+
let result = find_folder_path("routes").unwrap();
728731

729732
// Restore CARGO_MANIFEST_DIR
730733
if let Some(old_value) = old_manifest_dir {

0 commit comments

Comments
 (0)