@@ -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