@@ -700,6 +700,7 @@ mod tests {
700700 use rstest:: rstest;
701701 use std:: collections:: HashMap ;
702702 use vespera_core:: schema:: { SchemaRef , SchemaType } ;
703+ use insta:: assert_debug_snapshot;
703704
704705 #[ rstest]
705706 #[ case( "HashMap<String, i32>" , Some ( SchemaType :: Object ) , true ) ]
@@ -724,6 +725,175 @@ mod tests {
724725 }
725726 }
726727
728+ #[ rstest]
729+ #[ case(
730+ r#"
731+ #[serde(rename_all = "kebab-case")]
732+ enum Status {
733+ #[serde(rename = "ok-status")]
734+ Ok,
735+ ErrorCode,
736+ }
737+ "# ,
738+ SchemaType :: String ,
739+ vec![ "ok-status" , "ErrorCode" ] // rename_all is not applied in this path
740+ ) ]
741+ fn test_parse_enum_to_schema_unit_variants (
742+ #[ case] enum_src : & str ,
743+ #[ case] expected_type : SchemaType ,
744+ #[ case] expected_enum : Vec < & str > ,
745+ ) {
746+ let enum_item: syn:: ItemEnum = syn:: parse_str ( enum_src) . unwrap ( ) ;
747+ let schema = parse_enum_to_schema ( & enum_item, & HashMap :: new ( ) , & HashMap :: new ( ) ) ;
748+ assert_eq ! ( schema. schema_type, Some ( expected_type) ) ;
749+ let got = schema. clone ( )
750+ . r#enum
751+ . unwrap ( )
752+ . iter ( )
753+ . map ( |v| v. as_str ( ) . unwrap ( ) . to_string ( ) )
754+ . collect :: < Vec < _ > > ( ) ;
755+ assert_eq ! ( got, expected_enum) ;
756+ assert_debug_snapshot ! ( schema) ;
757+ }
758+
759+ #[ rstest]
760+ #[ case(
761+ r#"
762+ enum Event {
763+ Data(String),
764+ }
765+ "# ,
766+ 1 ,
767+ Some ( SchemaType :: String ) ,
768+ 0 // single-field tuple variant stored as object with inline schema
769+ ) ]
770+ #[ case(
771+ r#"
772+ enum Pair {
773+ Values(i32, String),
774+ }
775+ "# ,
776+ 1 ,
777+ Some ( SchemaType :: Array ) ,
778+ 2 // tuple array prefix_items length
779+ ) ]
780+ #[ case(
781+ r#"
782+ enum Msg {
783+ Detail { id: i32, note: Option<String> },
784+ }
785+ "# ,
786+ 1 ,
787+ Some ( SchemaType :: Object ) ,
788+ 0 // not an array; ignore prefix_items length
789+ ) ]
790+ fn test_parse_enum_to_schema_tuple_and_named_variants (
791+ #[ case] enum_src : & str ,
792+ #[ case] expected_one_of_len : usize ,
793+ #[ case] expected_inner_type : Option < SchemaType > ,
794+ #[ case] expected_prefix_items_len : usize ,
795+ ) {
796+ let enum_item: syn:: ItemEnum = syn:: parse_str ( enum_src) . unwrap ( ) ;
797+ let schema = parse_enum_to_schema ( & enum_item, & HashMap :: new ( ) , & HashMap :: new ( ) ) ;
798+ let one_of = schema. clone ( ) . one_of . expect ( "one_of missing" ) ;
799+ assert_eq ! ( one_of. len( ) , expected_one_of_len) ;
800+
801+ if let Some ( inner_expected) = expected_inner_type {
802+ if let SchemaRef :: Inline ( obj) = & one_of[ 0 ] {
803+ let props = obj. properties . as_ref ( ) . expect ( "props missing" ) ;
804+ // take first property value
805+ let inner_schema = props. values ( ) . next ( ) . expect ( "no property value" ) ;
806+ match inner_expected {
807+ SchemaType :: Array => {
808+ if let SchemaRef :: Inline ( array_schema) = inner_schema {
809+ assert_eq ! ( array_schema. schema_type, Some ( SchemaType :: Array ) ) ;
810+ if expected_prefix_items_len > 0 {
811+ assert_eq ! (
812+ array_schema. prefix_items. as_ref( ) . unwrap( ) . len( ) ,
813+ expected_prefix_items_len
814+ ) ;
815+ }
816+ } else {
817+ panic ! ( "Expected inline array schema" ) ;
818+ }
819+ }
820+ SchemaType :: Object => {
821+ if let SchemaRef :: Inline ( inner_obj) = inner_schema {
822+ assert_eq ! ( inner_obj. schema_type, Some ( SchemaType :: Object ) ) ;
823+ let inner_props = inner_obj. properties . as_ref ( ) . unwrap ( ) ;
824+ assert ! ( inner_props. contains_key( "id" ) ) ;
825+ assert ! ( inner_props. contains_key( "note" ) ) ;
826+ assert ! ( inner_obj. required. as_ref( ) . unwrap( ) . contains( & "id" . to_string( ) ) ) ;
827+ } else {
828+ panic ! ( "Expected inline object schema" ) ;
829+ }
830+ }
831+ _ => { }
832+ }
833+ } else {
834+ panic ! ( "Expected inline schema in one_of" ) ;
835+ }
836+ }
837+
838+ assert_debug_snapshot ! ( schema) ;
839+ }
840+
841+ #[ test]
842+ fn test_parse_struct_to_schema_required_optional ( ) {
843+ let struct_item: syn:: ItemStruct = syn:: parse_str (
844+ r#"
845+ struct User {
846+ id: i32,
847+ name: Option<String>,
848+ }
849+ "# ,
850+ )
851+ . unwrap ( ) ;
852+ let schema = parse_struct_to_schema ( & struct_item, & HashMap :: new ( ) , & HashMap :: new ( ) ) ;
853+ let props = schema. properties . as_ref ( ) . unwrap ( ) ;
854+ assert ! ( props. contains_key( "id" ) ) ;
855+ assert ! ( props. contains_key( "name" ) ) ;
856+ assert ! ( schema. required. as_ref( ) . unwrap( ) . contains( & "id" . to_string( ) ) ) ;
857+ assert ! ( !schema. required. as_ref( ) . unwrap( ) . contains( & "name" . to_string( ) ) ) ;
858+ }
859+
860+ #[ test]
861+ fn test_parse_type_to_schema_ref_empty_path_and_reference ( ) {
862+ // Empty path segments returns object
863+ let ty = Type :: Path ( syn:: TypePath {
864+ qself : None ,
865+ path : syn:: Path {
866+ leading_colon : None ,
867+ segments : syn:: punctuated:: Punctuated :: new ( ) ,
868+ } ,
869+ } ) ;
870+ let schema_ref = parse_type_to_schema_ref ( & ty, & HashMap :: new ( ) , & HashMap :: new ( ) ) ;
871+ assert ! ( matches!( schema_ref, SchemaRef :: Inline ( _) ) ) ;
872+
873+ // Reference type delegates to inner
874+ let ty: Type = syn:: parse_str ( "&i32" ) . unwrap ( ) ;
875+ let schema_ref = parse_type_to_schema_ref ( & ty, & HashMap :: new ( ) , & HashMap :: new ( ) ) ;
876+ if let SchemaRef :: Inline ( schema) = schema_ref {
877+ assert_eq ! ( schema. schema_type, Some ( SchemaType :: Integer ) ) ;
878+ } else {
879+ panic ! ( "Expected inline integer schema" ) ;
880+ }
881+ }
882+
883+ #[ test]
884+ fn test_parse_type_to_schema_ref_known_schema_ref_and_unknown_custom ( ) {
885+ let mut known_schemas = HashMap :: new ( ) ;
886+ known_schemas. insert ( "Known" . to_string ( ) , "Known" . to_string ( ) ) ;
887+
888+ let ty: Type = syn:: parse_str ( "Known" ) . unwrap ( ) ;
889+ let schema_ref = parse_type_to_schema_ref ( & ty, & known_schemas, & HashMap :: new ( ) ) ;
890+ assert ! ( matches!( schema_ref, SchemaRef :: Ref ( _) ) ) ;
891+
892+ let ty: Type = syn:: parse_str ( "UnknownType" ) . unwrap ( ) ;
893+ let schema_ref = parse_type_to_schema_ref ( & ty, & known_schemas, & HashMap :: new ( ) ) ;
894+ assert ! ( matches!( schema_ref, SchemaRef :: Inline ( _) ) ) ;
895+ }
896+
727897 #[ test]
728898 fn test_parse_type_to_schema_ref_generic_substitution ( ) {
729899 // Ensure generic struct Wrapper<T> { value: T } is substituted to concrete type
0 commit comments