Skip to content

Commit 997e00b

Browse files
committed
Add coverage
1 parent 164f640 commit 997e00b

2 files changed

Lines changed: 508 additions & 0 deletions

File tree

crates/vespera_macro/src/parser/schema/type_schema.rs

Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1009,4 +1009,317 @@ mod tests {
10091009
panic!("Expected inline schema for TimeRange");
10101010
}
10111011
}
1012+
1013+
#[rstest]
1014+
#[case("i8")]
1015+
#[case("i16")]
1016+
#[case("i32")]
1017+
#[case("i64")]
1018+
#[case("u8")]
1019+
#[case("u16")]
1020+
#[case("u32")]
1021+
#[case("u64")]
1022+
#[case("f32")]
1023+
#[case("f64")]
1024+
#[case("bool")]
1025+
#[case("String")]
1026+
#[case("str")]
1027+
fn test_is_primitive_type_positive(#[case] ty_src: &str) {
1028+
let ty: Type = syn::parse_str(ty_src).unwrap();
1029+
assert!(is_primitive_type(&ty), "{ty_src} should be primitive");
1030+
}
1031+
1032+
#[rstest]
1033+
#[case("Vec")]
1034+
#[case("Option")]
1035+
#[case("HashMap")]
1036+
#[case("MyStruct")]
1037+
fn test_is_primitive_type_negative_single_segment(#[case] ty_src: &str) {
1038+
let ty: Type = syn::parse_str(ty_src).unwrap();
1039+
assert!(!is_primitive_type(&ty), "{ty_src} should NOT be primitive");
1040+
}
1041+
1042+
#[test]
1043+
fn test_is_primitive_type_tuple_type() {
1044+
let ty: Type = syn::parse_str("(i32, bool)").unwrap();
1045+
assert!(!is_primitive_type(&ty));
1046+
}
1047+
1048+
// ========== Coverage: FieldData / NamedTempFile binary format ==========
1049+
1050+
#[test]
1051+
fn test_parse_type_field_data_binary_format() {
1052+
let ty: Type = syn::parse_str("FieldData").unwrap();
1053+
let schema_ref = parse_type_to_schema_ref(&ty, &HashSet::new(), &HashMap::new());
1054+
if let SchemaRef::Inline(schema) = schema_ref {
1055+
assert_eq!(schema.schema_type, Some(SchemaType::String));
1056+
assert_eq!(schema.format, Some("binary".to_string()));
1057+
} else {
1058+
panic!("Expected inline schema for FieldData");
1059+
}
1060+
}
1061+
1062+
#[test]
1063+
fn test_parse_type_named_temp_file_binary_format() {
1064+
let ty: Type = syn::parse_str("NamedTempFile").unwrap();
1065+
let schema_ref = parse_type_to_schema_ref(&ty, &HashSet::new(), &HashMap::new());
1066+
if let SchemaRef::Inline(schema) = schema_ref {
1067+
assert_eq!(schema.schema_type, Some(SchemaType::String));
1068+
assert_eq!(schema.format, Some("binary".to_string()));
1069+
} else {
1070+
panic!("Expected inline schema for NamedTempFile");
1071+
}
1072+
}
1073+
1074+
// ========== Coverage: non-generic wrapper types without angle brackets ==========
1075+
1076+
#[rstest]
1077+
#[case("Option")]
1078+
#[case("Result")]
1079+
#[case("Json")]
1080+
#[case("Path")]
1081+
#[case("Query")]
1082+
#[case("Header")]
1083+
fn test_parse_type_non_generic_wrappers_return_object(#[case] ty_src: &str) {
1084+
let ty: Type = syn::parse_str(ty_src).unwrap();
1085+
let schema_ref = parse_type_to_schema_ref(&ty, &HashSet::new(), &HashMap::new());
1086+
if let SchemaRef::Inline(schema) = schema_ref {
1087+
assert_eq!(
1088+
schema.schema_type,
1089+
Some(SchemaType::Object),
1090+
"{ty_src} without generics should be object"
1091+
);
1092+
} else {
1093+
panic!("Expected inline schema for {ty_src}");
1094+
}
1095+
}
1096+
1097+
// ========== Coverage: recursion depth limit ==========
1098+
1099+
#[test]
1100+
fn test_recursion_depth_limit_returns_object() {
1101+
SCHEMA_RECURSION_DEPTH.with(|depth| {
1102+
let previous = depth.get();
1103+
depth.set(MAX_SCHEMA_RECURSION_DEPTH);
1104+
let ty: Type = syn::parse_str("String").unwrap();
1105+
let schema_ref =
1106+
parse_type_to_schema_ref_with_schemas(&ty, &HashSet::new(), &HashMap::new());
1107+
// Should return object fallback, NOT string
1108+
if let SchemaRef::Inline(schema) = &schema_ref {
1109+
assert_eq!(schema.schema_type, Some(SchemaType::Object));
1110+
} else {
1111+
panic!("Expected inline object schema at max recursion depth");
1112+
}
1113+
// Restore
1114+
depth.set(previous);
1115+
});
1116+
}
1117+
1118+
#[test]
1119+
fn test_recursion_depth_resets_after_call() {
1120+
SCHEMA_RECURSION_DEPTH.with(|depth| {
1121+
assert_eq!(depth.get(), 0, "Depth should start at 0");
1122+
});
1123+
let ty: Type = syn::parse_str("Vec<Option<String>>").unwrap();
1124+
let _ = parse_type_to_schema_ref_with_schemas(&ty, &HashSet::new(), &HashMap::new());
1125+
SCHEMA_RECURSION_DEPTH.with(|depth| {
1126+
assert_eq!(depth.get(), 0, "Depth should reset to 0 after call");
1127+
});
1128+
}
1129+
1130+
// ========== Coverage: generic known schema edge cases ==========
1131+
1132+
#[test]
1133+
fn test_generic_known_schema_no_struct_definition() {
1134+
// Known schema with angle brackets but NO struct_definitions entry → falls through to Ref
1135+
let mut known = HashSet::new();
1136+
known.insert("Wrapper".to_string());
1137+
// Do NOT insert into struct_definitions
1138+
let ty: Type = syn::parse_str("Wrapper<String>").unwrap();
1139+
let schema_ref = parse_type_to_schema_ref(&ty, &known, &HashMap::new());
1140+
// Should fall through to non-generic ref path
1141+
assert!(
1142+
matches!(schema_ref, SchemaRef::Ref(_)),
1143+
"Should be a $ref when no struct definition found"
1144+
);
1145+
}
1146+
1147+
#[test]
1148+
fn test_generic_known_schema_param_count_mismatch() {
1149+
// Struct has 1 generic param but 2 concrete types provided → falls through to Ref
1150+
let mut known = HashSet::new();
1151+
known.insert("Single".to_string());
1152+
let mut defs = HashMap::new();
1153+
defs.insert(
1154+
"Single".to_string(),
1155+
"struct Single<T> { value: T }".to_string(),
1156+
);
1157+
1158+
let ty: Type = syn::parse_str("Single<String, i32>").unwrap();
1159+
let schema_ref = parse_type_to_schema_ref(&ty, &known, &defs);
1160+
assert!(
1161+
matches!(schema_ref, SchemaRef::Ref(_)),
1162+
"Mismatched param count should fall through to $ref"
1163+
);
1164+
}
1165+
1166+
#[test]
1167+
fn test_generic_known_schema_invalid_definition() {
1168+
// struct_definitions has invalid Rust code → parse fails → falls through to Ref
1169+
let mut known = HashSet::new();
1170+
known.insert("Bad".to_string());
1171+
let mut defs = HashMap::new();
1172+
defs.insert("Bad".to_string(), "not valid rust code!!!".to_string());
1173+
1174+
let ty: Type = syn::parse_str("Bad<String>").unwrap();
1175+
let schema_ref = parse_type_to_schema_ref(&ty, &known, &defs);
1176+
assert!(
1177+
matches!(schema_ref, SchemaRef::Ref(_)),
1178+
"Invalid definition should fall through to $ref"
1179+
);
1180+
}
1181+
1182+
#[test]
1183+
fn test_generic_known_schema_tuple_struct() {
1184+
// Tuple struct fields are NOT Named → skips field substitution but still inlines
1185+
let mut known = HashSet::new();
1186+
known.insert("Pair".to_string());
1187+
let mut defs = HashMap::new();
1188+
defs.insert("Pair".to_string(), "struct Pair<T>(T, T);".to_string());
1189+
1190+
let ty: Type = syn::parse_str("Pair<String>").unwrap();
1191+
let schema_ref = parse_type_to_schema_ref(&ty, &known, &defs);
1192+
// Tuple struct still gets inlined (generics cleared, parse_struct_to_schema called)
1193+
// but field types are NOT substituted (no Named fields to iterate)
1194+
assert!(
1195+
matches!(schema_ref, SchemaRef::Inline(_)),
1196+
"Tuple struct should still inline"
1197+
);
1198+
}
1199+
1200+
#[test]
1201+
fn test_generic_known_schema_no_generic_params_in_def() {
1202+
// Struct definition has no generics but concrete type has angle brackets → mismatch
1203+
let mut known = HashSet::new();
1204+
known.insert("Plain".to_string());
1205+
let mut defs = HashMap::new();
1206+
defs.insert("Plain".to_string(), "struct Plain { x: i32 }".to_string());
1207+
1208+
let ty: Type = syn::parse_str("Plain<String>").unwrap();
1209+
let schema_ref = parse_type_to_schema_ref(&ty, &known, &defs);
1210+
// 0 generic params != 1 concrete type → falls through to Ref
1211+
assert!(matches!(schema_ref, SchemaRef::Ref(_)));
1212+
}
1213+
1214+
// ========== Coverage: nested generic types ==========
1215+
1216+
#[test]
1217+
fn test_nested_vec_vec_string() {
1218+
let ty: Type = syn::parse_str("Vec<Vec<String>>").unwrap();
1219+
let schema_ref = parse_type_to_schema_ref(&ty, &HashSet::new(), &HashMap::new());
1220+
if let SchemaRef::Inline(schema) = &schema_ref {
1221+
assert_eq!(schema.schema_type, Some(SchemaType::Array));
1222+
if let Some(SchemaRef::Inline(inner)) = schema.items.as_deref() {
1223+
assert_eq!(inner.schema_type, Some(SchemaType::Array));
1224+
if let Some(SchemaRef::Inline(innermost)) = inner.items.as_deref() {
1225+
assert_eq!(innermost.schema_type, Some(SchemaType::String));
1226+
} else {
1227+
panic!("Expected innermost inline schema");
1228+
}
1229+
} else {
1230+
panic!("Expected inner inline schema");
1231+
}
1232+
} else {
1233+
panic!("Expected inline schema for nested Vec");
1234+
}
1235+
}
1236+
1237+
#[test]
1238+
fn test_option_vec_i32() {
1239+
let ty: Type = syn::parse_str("Option<Vec<i32>>").unwrap();
1240+
let schema_ref = parse_type_to_schema_ref(&ty, &HashSet::new(), &HashMap::new());
1241+
if let SchemaRef::Inline(schema) = &schema_ref {
1242+
assert_eq!(schema.schema_type, Some(SchemaType::Array));
1243+
assert_eq!(schema.nullable, Some(true));
1244+
if let Some(SchemaRef::Inline(items)) = schema.items.as_deref() {
1245+
assert_eq!(items.schema_type, Some(SchemaType::Integer));
1246+
} else {
1247+
panic!("Expected inline items");
1248+
}
1249+
} else {
1250+
panic!("Expected inline schema for Option<Vec<i32>>");
1251+
}
1252+
}
1253+
1254+
#[test]
1255+
fn test_box_box_i32() {
1256+
// Box<Box<i32>> → transparent twice → integer
1257+
let ty: Type = syn::parse_str("Box<Box<i32>>").unwrap();
1258+
let schema_ref = parse_type_to_schema_ref(&ty, &HashSet::new(), &HashMap::new());
1259+
if let SchemaRef::Inline(schema) = &schema_ref {
1260+
assert_eq!(schema.schema_type, Some(SchemaType::Integer));
1261+
} else {
1262+
panic!("Expected inline integer schema for Box<Box<i32>>");
1263+
}
1264+
}
1265+
1266+
// ========== Coverage: HashMap/BTreeMap with known ref value ==========
1267+
1268+
#[test]
1269+
fn test_hashmap_with_known_ref_value() {
1270+
let mut known = HashSet::new();
1271+
known.insert("User".to_string());
1272+
let ty: Type = syn::parse_str("HashMap<String, User>").unwrap();
1273+
let schema_ref = parse_type_to_schema_ref(&ty, &known, &HashMap::new());
1274+
if let SchemaRef::Inline(schema) = &schema_ref {
1275+
assert_eq!(schema.schema_type, Some(SchemaType::Object));
1276+
let additional = schema.additional_properties.as_ref().unwrap();
1277+
assert_eq!(additional.get("$ref").unwrap(), "#/components/schemas/User");
1278+
} else {
1279+
panic!("Expected inline schema for HashMap<String, User>");
1280+
}
1281+
}
1282+
1283+
#[test]
1284+
fn test_btreemap_with_inline_value() {
1285+
let ty: Type = syn::parse_str("BTreeMap<String, Vec<i32>>").unwrap();
1286+
let schema_ref = parse_type_to_schema_ref(&ty, &HashSet::new(), &HashMap::new());
1287+
if let SchemaRef::Inline(schema) = &schema_ref {
1288+
assert_eq!(schema.schema_type, Some(SchemaType::Object));
1289+
let additional = schema.additional_properties.as_ref().unwrap();
1290+
// Value should be an array schema serialized
1291+
assert_eq!(additional.get("type").unwrap(), "array");
1292+
} else {
1293+
panic!("Expected inline schema for BTreeMap with Vec value");
1294+
}
1295+
}
1296+
1297+
// ========== Coverage: HashMap/BTreeMap with insufficient args ==========
1298+
1299+
#[test]
1300+
fn test_hashmap_single_arg_falls_through() {
1301+
// HashMap<String> — only 1 type arg, need 2 → falls through to unknown type
1302+
let ty: Type = syn::parse_str("HashMap<String>").unwrap();
1303+
let schema_ref = parse_type_to_schema_ref(&ty, &HashSet::new(), &HashMap::new());
1304+
if let SchemaRef::Inline(schema) = &schema_ref {
1305+
assert_eq!(schema.schema_type, Some(SchemaType::Object));
1306+
// Should NOT have additional_properties since it fell through
1307+
assert!(schema.additional_properties.is_none());
1308+
} else {
1309+
panic!("Expected inline schema");
1310+
}
1311+
}
1312+
1313+
// ========== Coverage: &mut T reference ==========
1314+
1315+
#[test]
1316+
fn test_mutable_reference_delegates_to_inner() {
1317+
let ty: Type = syn::parse_str("&mut String").unwrap();
1318+
let schema_ref = parse_type_to_schema_ref(&ty, &HashSet::new(), &HashMap::new());
1319+
if let SchemaRef::Inline(schema) = &schema_ref {
1320+
assert_eq!(schema.schema_type, Some(SchemaType::String));
1321+
} else {
1322+
panic!("Expected inline string schema for &mut String");
1323+
}
1324+
}
10121325
}

0 commit comments

Comments
 (0)