Skip to content

Commit 20cef62

Browse files
committed
Support description
1 parent 0ffbc17 commit 20cef62

6 files changed

Lines changed: 479 additions & 44 deletions

File tree

crates/vespera_macro/src/lib.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2058,4 +2058,86 @@ pub fn get_users() -> String {
20582058
// It should return a PathBuf (either from src/nonexistent... or just the folder name)
20592059
assert!(result.to_string_lossy().contains("nonexistent_folder_xyz"));
20602060
}
2061+
2062+
// ========== Tests for extract_schema_name_attr ==========
2063+
2064+
#[test]
2065+
fn test_extract_schema_name_attr_with_name() {
2066+
let attrs: Vec<syn::Attribute> = syn::parse_quote! {
2067+
#[schema(name = "CustomName")]
2068+
};
2069+
let result = extract_schema_name_attr(&attrs);
2070+
assert_eq!(result, Some("CustomName".to_string()));
2071+
}
2072+
2073+
#[test]
2074+
fn test_extract_schema_name_attr_without_name() {
2075+
let attrs: Vec<syn::Attribute> = syn::parse_quote! {
2076+
#[derive(Debug)]
2077+
};
2078+
let result = extract_schema_name_attr(&attrs);
2079+
assert_eq!(result, None);
2080+
}
2081+
2082+
#[test]
2083+
fn test_extract_schema_name_attr_empty_schema() {
2084+
let attrs: Vec<syn::Attribute> = syn::parse_quote! {
2085+
#[schema]
2086+
};
2087+
let result = extract_schema_name_attr(&attrs);
2088+
assert_eq!(result, None);
2089+
}
2090+
2091+
#[test]
2092+
fn test_extract_schema_name_attr_with_other_attrs() {
2093+
let attrs: Vec<syn::Attribute> = syn::parse_quote! {
2094+
#[derive(Clone)]
2095+
#[schema(name = "MySchema")]
2096+
#[serde(rename_all = "camelCase")]
2097+
};
2098+
let result = extract_schema_name_attr(&attrs);
2099+
assert_eq!(result, Some("MySchema".to_string()));
2100+
}
2101+
2102+
// ========== Tests for process_derive_schema ==========
2103+
2104+
#[test]
2105+
fn test_process_derive_schema_simple() {
2106+
let input: syn::DeriveInput = syn::parse_quote! {
2107+
struct User {
2108+
id: i32,
2109+
name: String,
2110+
}
2111+
};
2112+
let (metadata, tokens) = process_derive_schema(&input);
2113+
assert_eq!(metadata.name, "User");
2114+
assert!(metadata.definition.contains("User"));
2115+
let tokens_str = tokens.to_string();
2116+
assert!(tokens_str.contains("SchemaBuilder"));
2117+
}
2118+
2119+
#[test]
2120+
fn test_process_derive_schema_with_custom_name() {
2121+
let input: syn::DeriveInput = syn::parse_quote! {
2122+
#[schema(name = "CustomUserSchema")]
2123+
struct User {
2124+
id: i32,
2125+
}
2126+
};
2127+
let (metadata, _) = process_derive_schema(&input);
2128+
assert_eq!(metadata.name, "CustomUserSchema");
2129+
}
2130+
2131+
#[test]
2132+
fn test_process_derive_schema_with_generics() {
2133+
let input: syn::DeriveInput = syn::parse_quote! {
2134+
struct Container<T> {
2135+
value: T,
2136+
}
2137+
};
2138+
let (metadata, tokens) = process_derive_schema(&input);
2139+
assert_eq!(metadata.name, "Container");
2140+
let tokens_str = tokens.to_string();
2141+
assert!(tokens_str.contains("< T >") || tokens_str.contains("<T>"));
2142+
}
20612143
}

crates/vespera_macro/src/parser/schema.rs

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2937,4 +2937,218 @@ mod tests {
29372937
let result = rename_field("get_user_by_id", Some("camelCase"));
29382938
assert_eq!(result, "getUserById");
29392939
}
2940+
2941+
// Tests for extract_doc_comment function
2942+
#[test]
2943+
fn test_extract_doc_comment_single_line() {
2944+
let attrs: Vec<syn::Attribute> = syn::parse_quote! {
2945+
#[doc = " This is a doc comment"]
2946+
};
2947+
let result = extract_doc_comment(&attrs);
2948+
assert_eq!(result, Some("This is a doc comment".to_string()));
2949+
}
2950+
2951+
#[test]
2952+
fn test_extract_doc_comment_multi_line() {
2953+
let attrs: Vec<syn::Attribute> = syn::parse_quote! {
2954+
#[doc = " First line"]
2955+
#[doc = " Second line"]
2956+
#[doc = " Third line"]
2957+
};
2958+
let result = extract_doc_comment(&attrs);
2959+
assert_eq!(
2960+
result,
2961+
Some("First line\nSecond line\nThird line".to_string())
2962+
);
2963+
}
2964+
2965+
#[test]
2966+
fn test_extract_doc_comment_no_leading_space() {
2967+
let attrs: Vec<syn::Attribute> = syn::parse_quote! {
2968+
#[doc = "No leading space"]
2969+
};
2970+
let result = extract_doc_comment(&attrs);
2971+
assert_eq!(result, Some("No leading space".to_string()));
2972+
}
2973+
2974+
#[test]
2975+
fn test_extract_doc_comment_empty() {
2976+
let attrs: Vec<syn::Attribute> = vec![];
2977+
let result = extract_doc_comment(&attrs);
2978+
assert_eq!(result, None);
2979+
}
2980+
2981+
#[test]
2982+
fn test_extract_doc_comment_with_non_doc_attrs() {
2983+
let attrs: Vec<syn::Attribute> = syn::parse_quote! {
2984+
#[derive(Debug)]
2985+
#[doc = " The doc comment"]
2986+
#[serde(rename = "test")]
2987+
};
2988+
let result = extract_doc_comment(&attrs);
2989+
assert_eq!(result, Some("The doc comment".to_string()));
2990+
}
2991+
2992+
// Tests for extract_schema_name_from_entity function
2993+
#[test]
2994+
fn test_extract_schema_name_from_entity_super_path() {
2995+
let ty: Type = syn::parse_str("super::user::Entity").unwrap();
2996+
let result = extract_schema_name_from_entity(&ty);
2997+
assert_eq!(result, Some("User".to_string()));
2998+
}
2999+
3000+
#[test]
3001+
fn test_extract_schema_name_from_entity_crate_path() {
3002+
let ty: Type = syn::parse_str("crate::models::memo::Entity").unwrap();
3003+
let result = extract_schema_name_from_entity(&ty);
3004+
assert_eq!(result, Some("Memo".to_string()));
3005+
}
3006+
3007+
#[test]
3008+
fn test_extract_schema_name_from_entity_not_entity() {
3009+
let ty: Type = syn::parse_str("crate::models::user::Model").unwrap();
3010+
let result = extract_schema_name_from_entity(&ty);
3011+
assert_eq!(result, None);
3012+
}
3013+
3014+
#[test]
3015+
fn test_extract_schema_name_from_entity_single_segment() {
3016+
let ty: Type = syn::parse_str("Entity").unwrap();
3017+
let result = extract_schema_name_from_entity(&ty);
3018+
assert_eq!(result, None);
3019+
}
3020+
3021+
#[test]
3022+
fn test_extract_schema_name_from_entity_non_path_type() {
3023+
let ty: Type = syn::parse_str("&str").unwrap();
3024+
let result = extract_schema_name_from_entity(&ty);
3025+
assert_eq!(result, None);
3026+
}
3027+
3028+
#[test]
3029+
fn test_extract_schema_name_from_entity_empty_module_name() {
3030+
// Tests the branch where module name has no characters (edge case)
3031+
let ty: Type = syn::parse_str("super::some_module::Entity").unwrap();
3032+
let result = extract_schema_name_from_entity(&ty);
3033+
assert_eq!(result, Some("Some_module".to_string()));
3034+
}
3035+
3036+
// Tests for enum with doc comments on variants
3037+
#[test]
3038+
fn test_parse_enum_to_schema_with_variant_descriptions() {
3039+
let enum_src = r#"
3040+
/// Enum description
3041+
enum Status {
3042+
/// Active variant
3043+
Active,
3044+
/// Inactive variant
3045+
Inactive,
3046+
}
3047+
"#;
3048+
let enum_item: syn::ItemEnum = syn::parse_str(enum_src).unwrap();
3049+
let schema = parse_enum_to_schema(&enum_item, &HashMap::new(), &HashMap::new());
3050+
assert_eq!(schema.description, Some("Enum description".to_string()));
3051+
}
3052+
3053+
#[test]
3054+
fn test_parse_enum_to_schema_data_variant_with_description() {
3055+
let enum_src = r#"
3056+
/// Data enum
3057+
enum Event {
3058+
/// Text event description
3059+
Text(String),
3060+
/// Number event description
3061+
Number(i32),
3062+
}
3063+
"#;
3064+
let enum_item: syn::ItemEnum = syn::parse_str(enum_src).unwrap();
3065+
let schema = parse_enum_to_schema(&enum_item, &HashMap::new(), &HashMap::new());
3066+
assert_eq!(schema.description, Some("Data enum".to_string()));
3067+
assert!(schema.one_of.is_some());
3068+
let one_of = schema.one_of.unwrap();
3069+
assert_eq!(one_of.len(), 2);
3070+
// Check first variant has description
3071+
if let SchemaRef::Inline(variant_schema) = &one_of[0] {
3072+
assert_eq!(
3073+
variant_schema.description,
3074+
Some("Text event description".to_string())
3075+
);
3076+
}
3077+
}
3078+
3079+
#[test]
3080+
fn test_parse_enum_to_schema_struct_variant_with_field_docs() {
3081+
let enum_src = r#"
3082+
enum Event {
3083+
/// Record variant
3084+
Record {
3085+
/// The value field
3086+
value: i32,
3087+
/// The name field
3088+
name: String,
3089+
},
3090+
}
3091+
"#;
3092+
let enum_item: syn::ItemEnum = syn::parse_str(enum_src).unwrap();
3093+
let schema = parse_enum_to_schema(&enum_item, &HashMap::new(), &HashMap::new());
3094+
assert!(schema.one_of.is_some());
3095+
let one_of = schema.one_of.unwrap();
3096+
if let SchemaRef::Inline(variant_schema) = &one_of[0] {
3097+
assert_eq!(
3098+
variant_schema.description,
3099+
Some("Record variant".to_string())
3100+
);
3101+
}
3102+
}
3103+
3104+
// Tests for struct with doc comments
3105+
#[test]
3106+
fn test_parse_struct_to_schema_with_description() {
3107+
let struct_src = r#"
3108+
/// User struct description
3109+
struct User {
3110+
/// User ID
3111+
id: i32,
3112+
/// User name
3113+
name: String,
3114+
}
3115+
"#;
3116+
let struct_item: syn::ItemStruct = syn::parse_str(struct_src).unwrap();
3117+
let schema = parse_struct_to_schema(&struct_item, &HashMap::new(), &HashMap::new());
3118+
assert_eq!(
3119+
schema.description,
3120+
Some("User struct description".to_string())
3121+
);
3122+
// Check field descriptions
3123+
let props = schema.properties.unwrap();
3124+
if let SchemaRef::Inline(id_schema) = props.get("id").unwrap() {
3125+
assert_eq!(id_schema.description, Some("User ID".to_string()));
3126+
}
3127+
if let SchemaRef::Inline(name_schema) = props.get("name").unwrap() {
3128+
assert_eq!(name_schema.description, Some("User name".to_string()));
3129+
}
3130+
}
3131+
3132+
#[test]
3133+
fn test_parse_struct_to_schema_field_with_ref_and_description() {
3134+
let struct_src = r#"
3135+
struct Container {
3136+
/// The user reference
3137+
user: User,
3138+
}
3139+
"#;
3140+
let struct_item: syn::ItemStruct = syn::parse_str(struct_src).unwrap();
3141+
let mut known = HashMap::new();
3142+
known.insert("User".to_string(), "struct User { id: i32 }".to_string());
3143+
let schema = parse_struct_to_schema(&struct_item, &known, &HashMap::new());
3144+
let props = schema.properties.unwrap();
3145+
// Field with $ref and description should use allOf
3146+
if let SchemaRef::Inline(user_schema) = props.get("user").unwrap() {
3147+
assert_eq!(
3148+
user_schema.description,
3149+
Some("The user reference".to_string())
3150+
);
3151+
assert!(user_schema.all_of.is_some());
3152+
}
3153+
}
29403154
}

0 commit comments

Comments
 (0)