Skip to content

Commit 6e67932

Browse files
committed
Add testcase
1 parent 2cead44 commit 6e67932

5 files changed

Lines changed: 839 additions & 0 deletions

crates/vespera_macro/src/parser/schema.rs

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)