Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .changepacks/changepack_log_HkIOQGN5zIEAUT5Au3KyO.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"changes":{"Cargo.toml":"Patch"},"note":"Optimize","date":"2026-02-17T19:54:11.651834500Z"}
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ resolver = "2"
members = ["crates/*", "examples/*"]

[workspace.package]
version = "0.1.34"
version = "0.1.33"
edition = "2024"
license = "Apache-2.0"
repository = "https://github.com/dev-five-git/vespera"
readme = "README.md"

[workspace.dependencies]
vespera_core = { path = "crates/vespera_core", version = "0.1.34" }
vespera_macro = { path = "crates/vespera_macro", version = "0.1.34" }
vespera_core = { path = "crates/vespera_core", version = "0.1.33" }
vespera_macro = { path = "crates/vespera_macro", version = "0.1.33" }

[workspace.lints.clippy]
all = { level = "warn", priority = -1 }
Expand Down
7 changes: 1 addition & 6 deletions crates/vespera_macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,12 +283,7 @@ pub fn export_app(input: TokenStream) -> TokenStream {
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") else {
return syn::Error::new(
proc_macro2::Span::call_site(),
"export_app! macro: CARGO_MANIFEST_DIR is not set. This macro must be used within a cargo build.",
)
.to_compile_error()
.into();
return syn::Error::new(proc_macro2::Span::call_site(), "export_app! macro: CARGO_MANIFEST_DIR is not set. This macro must be used within a cargo build.").to_compile_error().into();
};

match process_export_app(&name, &folder_name, &schema_storage, &manifest_dir) {
Expand Down
92 changes: 92 additions & 0 deletions crates/vespera_macro/src/openapi_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1346,6 +1346,98 @@ pub fn get_user() -> User {
assert!(value.is_none());
}

#[test]
fn test_build_path_items_unknown_http_method() {
// Test lines 131-134: route with unknown HTTP method is skipped
let temp_dir = TempDir::new().expect("Failed to create temp dir");

let route_content = r#"
pub fn get_users() -> String {
"users".to_string()
}
"#;
let route_file = create_temp_file(&temp_dir, "users.rs", route_content);

let mut metadata = CollectedMetadata::new();
metadata.routes.push(RouteMetadata {
method: "INVALID".to_string(),
path: "/users".to_string(),
function_name: "get_users".to_string(),
module_path: "test::users".to_string(),
file_path: route_file.to_string_lossy().to_string(),
signature: "fn get_users() -> String".to_string(),
error_status: None,
tags: None,
description: None,
});

let doc = generate_openapi_doc_with_metadata(None, None, None, &metadata);

// Route with unknown HTTP method should be skipped entirely
assert!(
doc.paths.is_empty(),
"Route with unknown HTTP method should be skipped"
);
}

#[test]
fn test_build_path_items_unknown_method_skipped_valid_kept() {
// Test that unknown methods are skipped while valid routes are kept
let temp_dir = TempDir::new().expect("Failed to create temp dir");

let route_content = r#"
pub fn get_users() -> String {
"users".to_string()
}

pub fn create_users() -> String {
"created".to_string()
}
"#;
let route_file = create_temp_file(&temp_dir, "users.rs", route_content);
let file_path = route_file.to_string_lossy().to_string();

let mut metadata = CollectedMetadata::new();
// Invalid method route
metadata.routes.push(RouteMetadata {
method: "CONNECT".to_string(),
path: "/users".to_string(),
function_name: "get_users".to_string(),
module_path: "test::users".to_string(),
file_path: file_path.clone(),
signature: "fn get_users() -> String".to_string(),
error_status: None,
tags: None,
description: None,
});
// Valid method route
metadata.routes.push(RouteMetadata {
method: "POST".to_string(),
path: "/users".to_string(),
function_name: "create_users".to_string(),
module_path: "test::users".to_string(),
file_path,
signature: "fn create_users() -> String".to_string(),
error_status: None,
tags: None,
description: None,
});

let doc = generate_openapi_doc_with_metadata(None, None, None, &metadata);

// Only the valid POST route should appear
assert_eq!(doc.paths.len(), 1);
let path_item = doc.paths.get("/users").unwrap();
assert!(
path_item.post.is_some(),
"Valid POST route should be present"
);
assert!(
path_item.get.is_none(),
"Invalid method route should be skipped"
);
}

#[test]
fn test_generate_openapi_with_unparseable_definition() {
// Test line 42: syn::parse_str fails with invalid Rust syntax
Expand Down
84 changes: 82 additions & 2 deletions crates/vespera_macro/src/router_codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,9 @@
use proc_macro2::Span;
use quote::quote;
use syn::{
bracketed,
LitStr, bracketed,
parse::{Parse, ParseStream},
punctuated::Punctuated,
LitStr,
};
use vespera_core::{openapi::Server, route::HttpMethod};

Expand Down Expand Up @@ -1262,6 +1261,87 @@ pub fn get_users() -> String {
assert!(result.is_err());
}

#[test]
fn test_generate_router_code_unknown_http_method() {
// Test lines 337-340: route with unknown HTTP method is skipped in router codegen
let mut metadata = CollectedMetadata {
routes: Vec::new(),
structs: Vec::new(),
};
metadata.routes.push(crate::metadata::RouteMetadata {
method: "INVALID".to_string(),
path: "/users".to_string(),
function_name: "get_users".to_string(),
module_path: "routes::users".to_string(),
file_path: "dummy.rs".to_string(),
signature: "fn get_users() -> String".to_string(),
error_status: None,
tags: None,
description: None,
});

let result = generate_router_code(&metadata, None, None, &[]);
let code = result.to_string();

// Router should be generated but without any route calls
assert!(
code.contains("Router") && code.contains("new"),
"Code should contain Router::new(), got: {code}"
);
assert!(
!code.contains(". route ("),
"Route with unknown HTTP method should be skipped, got: {code}"
);
}

#[test]
fn test_generate_router_code_unknown_method_skipped_valid_kept() {
// Test that unknown methods are skipped while valid routes are still generated
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let folder_name = "routes";

create_temp_file(
&temp_dir,
"users.rs",
r#"
#[route(get)]
pub fn get_users() -> String {
"users".to_string()
}
"#,
);

let mut metadata = collect_metadata(temp_dir.path(), folder_name).unwrap();
// Inject an additional route with invalid method
metadata.routes.push(crate::metadata::RouteMetadata {
method: "CONNECT".to_string(),
path: "/invalid".to_string(),
function_name: "connect_handler".to_string(),
module_path: "routes::invalid".to_string(),
file_path: "dummy.rs".to_string(),
signature: "fn connect_handler() -> String".to_string(),
error_status: None,
tags: None,
description: None,
});

let result = generate_router_code(&metadata, None, None, &[]);
let code = result.to_string();

// Valid route should be present
assert!(
code.contains("get_users"),
"Valid route should be present, got: {code}"
);
// Invalid route should be skipped
assert!(
!code.contains("connect_handler"),
"Invalid method route should be skipped, got: {code}"
);

drop(temp_dir);
}

#[test]
fn test_auto_router_input_parse_invalid_token() {
// Test line 149: neither ident nor string literal triggers lookahead error
Expand Down
1 change: 0 additions & 1 deletion crates/vespera_macro/src/schema_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,6 @@ mod tests {
assert_eq!(metadata.name, "Container");
}


#[test]
fn test_extract_schema_name_attr_non_name_meta_key() {
// #[schema(other = "foo")] — has schema attr but no "name" key
Expand Down