Skip to content

Commit 545b968

Browse files
authored
Merge pull request #45 from dev-five-git/support-rename-all
Support rename all
2 parents 5c2e015 + 443cf1e commit 545b968

7 files changed

Lines changed: 1248 additions & 344 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"changes":{"crates/vespera_macro/Cargo.toml":"Patch","crates/vespera_core/Cargo.toml":"Patch","crates/vespera/Cargo.toml":"Patch"},"note":"Fix rename_all","date":"2026-01-12T04:26:20.440174200Z"}

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
target
22
lcov.info
3+
coverage
4+
build_rs_cov.profraw

crates/vespera_macro/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ fn parse_servers_values(input: ParseStream) -> syn::Result<Vec<ServerConfig>> {
266266
input.parse::<syn::Token![=]>()?;
267267

268268
if input.peek(syn::token::Bracket) {
269-
// Array format: [...]
269+
// Array format: [...]
270270
let content;
271271
let _ = bracketed!(content in input);
272272

crates/vespera_macro/src/openapi_generator.rs

Lines changed: 349 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -805,4 +805,353 @@ pub fn create_user() -> String {
805805
// Ensure TempDir is properly closed
806806
drop(temp_dir);
807807
}
808+
809+
#[test]
810+
fn test_generate_openapi_with_tags_and_description() {
811+
let temp_dir = TempDir::new().expect("Failed to create temp dir");
812+
let route_content = r#"
813+
pub fn get_users() -> String {
814+
"users".to_string()
815+
}
816+
"#;
817+
let route_file = create_temp_file(&temp_dir, "users.rs", route_content);
818+
819+
let mut metadata = CollectedMetadata::new();
820+
metadata.routes.push(RouteMetadata {
821+
method: "GET".to_string(),
822+
path: "/users".to_string(),
823+
function_name: "get_users".to_string(),
824+
module_path: "test::users".to_string(),
825+
file_path: route_file.to_string_lossy().to_string(),
826+
signature: "fn get_users() -> String".to_string(),
827+
error_status: Some(vec![404]),
828+
tags: Some(vec!["users".to_string(), "admin".to_string()]),
829+
description: Some("Get all users".to_string()),
830+
});
831+
832+
let doc = generate_openapi_doc_with_metadata(None, None, None, &metadata);
833+
834+
// Check route has description
835+
let path_item = doc.paths.get("/users").unwrap();
836+
let operation = path_item.get.as_ref().unwrap();
837+
assert_eq!(operation.description, Some("Get all users".to_string()));
838+
839+
// Check tags are collected
840+
assert!(doc.tags.is_some());
841+
let tags = doc.tags.as_ref().unwrap();
842+
assert!(tags.iter().any(|t| t.name == "users"));
843+
assert!(tags.iter().any(|t| t.name == "admin"));
844+
}
845+
846+
#[test]
847+
fn test_generate_openapi_with_servers() {
848+
let metadata = CollectedMetadata::new();
849+
let servers = vec![
850+
Server {
851+
url: "https://api.example.com".to_string(),
852+
description: Some("Production".to_string()),
853+
variables: None,
854+
},
855+
Server {
856+
url: "http://localhost:3000".to_string(),
857+
description: Some("Development".to_string()),
858+
variables: None,
859+
},
860+
];
861+
862+
let doc = generate_openapi_doc_with_metadata(None, None, Some(servers), &metadata);
863+
864+
assert!(doc.servers.is_some());
865+
let doc_servers = doc.servers.unwrap();
866+
assert_eq!(doc_servers.len(), 2);
867+
assert_eq!(doc_servers[0].url, "https://api.example.com");
868+
assert_eq!(doc_servers[1].url, "http://localhost:3000");
869+
}
870+
871+
#[test]
872+
fn test_extract_value_from_expr_int() {
873+
let expr: syn::Expr = syn::parse_str("42").unwrap();
874+
let value = extract_value_from_expr(&expr);
875+
assert_eq!(value, Some(serde_json::Value::Number(42.into())));
876+
}
877+
878+
#[test]
879+
fn test_extract_value_from_expr_float() {
880+
let expr: syn::Expr = syn::parse_str("12.34").unwrap();
881+
let value = extract_value_from_expr(&expr);
882+
assert!(value.is_some());
883+
if let Some(serde_json::Value::Number(n)) = value {
884+
assert!((n.as_f64().unwrap() - 12.34).abs() < 0.001);
885+
}
886+
}
887+
888+
#[test]
889+
fn test_extract_value_from_expr_bool() {
890+
let expr_true: syn::Expr = syn::parse_str("true").unwrap();
891+
let expr_false: syn::Expr = syn::parse_str("false").unwrap();
892+
assert_eq!(
893+
extract_value_from_expr(&expr_true),
894+
Some(serde_json::Value::Bool(true))
895+
);
896+
assert_eq!(
897+
extract_value_from_expr(&expr_false),
898+
Some(serde_json::Value::Bool(false))
899+
);
900+
}
901+
902+
#[test]
903+
fn test_extract_value_from_expr_string() {
904+
let expr: syn::Expr = syn::parse_str(r#""hello""#).unwrap();
905+
let value = extract_value_from_expr(&expr);
906+
assert_eq!(value, Some(serde_json::Value::String("hello".to_string())));
907+
}
908+
909+
#[test]
910+
fn test_extract_value_from_expr_to_string() {
911+
let expr: syn::Expr = syn::parse_str(r#""hello".to_string()"#).unwrap();
912+
let value = extract_value_from_expr(&expr);
913+
assert_eq!(value, Some(serde_json::Value::String("hello".to_string())));
914+
}
915+
916+
#[test]
917+
fn test_extract_value_from_expr_vec_macro() {
918+
let expr: syn::Expr = syn::parse_str("vec![]").unwrap();
919+
let value = extract_value_from_expr(&expr);
920+
assert_eq!(value, Some(serde_json::Value::Array(vec![])));
921+
}
922+
923+
#[test]
924+
fn test_extract_value_from_expr_unsupported() {
925+
// Binary expression is not supported
926+
let expr: syn::Expr = syn::parse_str("1 + 2").unwrap();
927+
let value = extract_value_from_expr(&expr);
928+
assert!(value.is_none());
929+
}
930+
931+
#[test]
932+
fn test_extract_value_from_expr_method_call_non_to_string() {
933+
// Method call that's not to_string()
934+
let expr: syn::Expr = syn::parse_str(r#""hello".len()"#).unwrap();
935+
let value = extract_value_from_expr(&expr);
936+
assert!(value.is_none());
937+
}
938+
939+
#[test]
940+
fn test_extract_value_from_expr_unsupported_literal() {
941+
// Byte literal is not directly supported
942+
let expr: syn::Expr = syn::parse_str("b'a'").unwrap();
943+
let value = extract_value_from_expr(&expr);
944+
assert!(value.is_none());
945+
}
946+
947+
#[test]
948+
fn test_extract_value_from_expr_non_vec_macro() {
949+
// Other macros like println! are not supported
950+
let expr: syn::Expr = syn::parse_str(r#"println!("test")"#).unwrap();
951+
let value = extract_value_from_expr(&expr);
952+
assert!(value.is_none());
953+
}
954+
955+
#[test]
956+
fn test_get_type_default_string() {
957+
let ty: syn::Type = syn::parse_str("String").unwrap();
958+
let value = get_type_default(&ty);
959+
assert_eq!(value, Some(serde_json::Value::String(String::new())));
960+
}
961+
962+
#[test]
963+
fn test_get_type_default_integers() {
964+
for type_name in &["i8", "i16", "i32", "i64", "u8", "u16", "u32", "u64"] {
965+
let ty: syn::Type = syn::parse_str(type_name).unwrap();
966+
let value = get_type_default(&ty);
967+
assert_eq!(
968+
value,
969+
Some(serde_json::Value::Number(0.into())),
970+
"Failed for type {}",
971+
type_name
972+
);
973+
}
974+
}
975+
976+
#[test]
977+
fn test_get_type_default_floats() {
978+
for type_name in &["f32", "f64"] {
979+
let ty: syn::Type = syn::parse_str(type_name).unwrap();
980+
let value = get_type_default(&ty);
981+
assert!(value.is_some(), "Failed for type {}", type_name);
982+
}
983+
}
984+
985+
#[test]
986+
fn test_get_type_default_bool() {
987+
let ty: syn::Type = syn::parse_str("bool").unwrap();
988+
let value = get_type_default(&ty);
989+
assert_eq!(value, Some(serde_json::Value::Bool(false)));
990+
}
991+
992+
#[test]
993+
fn test_get_type_default_unknown() {
994+
let ty: syn::Type = syn::parse_str("CustomType").unwrap();
995+
let value = get_type_default(&ty);
996+
assert!(value.is_none());
997+
}
998+
999+
#[test]
1000+
fn test_get_type_default_non_path() {
1001+
// Reference type is not a path type
1002+
let ty: syn::Type = syn::parse_str("&str").unwrap();
1003+
let value = get_type_default(&ty);
1004+
assert!(value.is_none());
1005+
}
1006+
1007+
#[test]
1008+
fn test_find_function_in_file() {
1009+
let file_content = r#"
1010+
fn foo() {}
1011+
fn bar() -> i32 { 42 }
1012+
fn baz(x: i32) -> i32 { x }
1013+
"#;
1014+
let file_ast: syn::File = syn::parse_str(file_content).unwrap();
1015+
1016+
assert!(find_function_in_file(&file_ast, "foo").is_some());
1017+
assert!(find_function_in_file(&file_ast, "bar").is_some());
1018+
assert!(find_function_in_file(&file_ast, "baz").is_some());
1019+
assert!(find_function_in_file(&file_ast, "nonexistent").is_none());
1020+
}
1021+
1022+
#[test]
1023+
fn test_extract_default_value_from_function() {
1024+
// Test direct expression return
1025+
let func: syn::ItemFn = syn::parse_str(
1026+
r#"
1027+
fn default_value() -> i32 {
1028+
42
1029+
}
1030+
"#,
1031+
)
1032+
.unwrap();
1033+
let value = extract_default_value_from_function(&func);
1034+
assert_eq!(value, Some(serde_json::Value::Number(42.into())));
1035+
}
1036+
1037+
#[test]
1038+
fn test_extract_default_value_from_function_with_return() {
1039+
// Test explicit return statement
1040+
let func: syn::ItemFn = syn::parse_str(
1041+
r#"
1042+
fn default_value() -> String {
1043+
return "hello".to_string()
1044+
}
1045+
"#,
1046+
)
1047+
.unwrap();
1048+
let value = extract_default_value_from_function(&func);
1049+
assert_eq!(value, Some(serde_json::Value::String("hello".to_string())));
1050+
}
1051+
1052+
#[test]
1053+
fn test_extract_default_value_from_function_empty() {
1054+
// Test function with no extractable value
1055+
let func: syn::ItemFn = syn::parse_str(
1056+
r#"
1057+
fn default_value() {
1058+
let x = 1;
1059+
}
1060+
"#,
1061+
)
1062+
.unwrap();
1063+
let value = extract_default_value_from_function(&func);
1064+
assert!(value.is_none());
1065+
}
1066+
1067+
#[test]
1068+
fn test_generate_openapi_with_default_functions() {
1069+
let temp_dir = TempDir::new().expect("Failed to create temp dir");
1070+
1071+
// Create a file with struct that has default function
1072+
let route_content = r#"
1073+
fn default_name() -> String {
1074+
"John".to_string()
1075+
}
1076+
1077+
struct User {
1078+
#[serde(default = "default_name")]
1079+
name: String,
1080+
}
1081+
1082+
pub fn get_user() -> User {
1083+
User { name: "Alice".to_string() }
1084+
}
1085+
"#;
1086+
let route_file = create_temp_file(&temp_dir, "user.rs", route_content);
1087+
1088+
let mut metadata = CollectedMetadata::new();
1089+
metadata.structs.push(StructMetadata {
1090+
name: "User".to_string(),
1091+
definition: r#"struct User { #[serde(default = "default_name")] name: String }"#
1092+
.to_string(),
1093+
});
1094+
metadata.routes.push(RouteMetadata {
1095+
method: "GET".to_string(),
1096+
path: "/user".to_string(),
1097+
function_name: "get_user".to_string(),
1098+
module_path: "test::user".to_string(),
1099+
file_path: route_file.to_string_lossy().to_string(),
1100+
signature: "fn get_user() -> User".to_string(),
1101+
error_status: None,
1102+
tags: None,
1103+
description: None,
1104+
});
1105+
1106+
let doc = generate_openapi_doc_with_metadata(None, None, None, &metadata);
1107+
1108+
// Struct should be present
1109+
assert!(doc.components.as_ref().unwrap().schemas.is_some());
1110+
let schemas = doc.components.as_ref().unwrap().schemas.as_ref().unwrap();
1111+
assert!(schemas.contains_key("User"));
1112+
}
1113+
1114+
#[test]
1115+
fn test_generate_openapi_with_simple_default() {
1116+
let temp_dir = TempDir::new().expect("Failed to create temp dir");
1117+
1118+
let route_content = r#"
1119+
struct Config {
1120+
#[serde(default)]
1121+
enabled: bool,
1122+
#[serde(default)]
1123+
count: i32,
1124+
}
1125+
1126+
pub fn get_config() -> Config {
1127+
Config { enabled: true, count: 0 }
1128+
}
1129+
"#;
1130+
let route_file = create_temp_file(&temp_dir, "config.rs", route_content);
1131+
1132+
let mut metadata = CollectedMetadata::new();
1133+
metadata.structs.push(StructMetadata {
1134+
name: "Config".to_string(),
1135+
definition:
1136+
r#"struct Config { #[serde(default)] enabled: bool, #[serde(default)] count: i32 }"#
1137+
.to_string(),
1138+
});
1139+
metadata.routes.push(RouteMetadata {
1140+
method: "GET".to_string(),
1141+
path: "/config".to_string(),
1142+
function_name: "get_config".to_string(),
1143+
module_path: "test::config".to_string(),
1144+
file_path: route_file.to_string_lossy().to_string(),
1145+
signature: "fn get_config() -> Config".to_string(),
1146+
error_status: None,
1147+
tags: None,
1148+
description: None,
1149+
});
1150+
1151+
let doc = generate_openapi_doc_with_metadata(None, None, None, &metadata);
1152+
1153+
assert!(doc.components.as_ref().unwrap().schemas.is_some());
1154+
let schemas = doc.components.as_ref().unwrap().schemas.as_ref().unwrap();
1155+
assert!(schemas.contains_key("Config"));
1156+
}
8081157
}

0 commit comments

Comments
 (0)