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