use std::sync::{LazyLock, Mutex};
use quote::quote;
use crate::metadata::StructMetadata;
#[cfg(not(tarpaulin_include))]
pub(crate) fn init_schema_storage() -> Mutex<Vec<StructMetadata>> {
Mutex::new(Vec::new())
}
pub(crate) static SCHEMA_STORAGE: LazyLock<Mutex<Vec<StructMetadata>>> =
LazyLock::new(init_schema_storage);
pub(crate) fn extract_schema_name_attr(attrs: &[syn::Attribute]) -> Option<String> {
for attr in attrs {
if attr.path().is_ident("schema") {
let mut custom_name = None;
let _ = attr.parse_nested_meta(|meta| {
if meta.path.is_ident("name") {
let value = meta.value()?;
let lit: syn::LitStr = value.parse()?;
custom_name = Some(lit.value());
}
Ok(())
});
if custom_name.is_some() {
return custom_name;
}
}
}
None
}
pub(crate) fn process_derive_schema(
input: &syn::DeriveInput,
) -> (StructMetadata, proc_macro2::TokenStream) {
let name = &input.ident;
let generics = &input.generics;
let schema_name = extract_schema_name_attr(&input.attrs).unwrap_or_else(|| name.to_string());
let metadata = StructMetadata::new(schema_name, quote::quote!(#input).to_string());
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let expanded = quote! {
impl #impl_generics vespera::schema::SchemaBuilder for #name #ty_generics #where_clause {}
};
(metadata, expanded)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_process_derive_schema_struct() {
let input: syn::DeriveInput = syn::parse_quote! {
struct User {
name: String,
age: u32,
}
};
let (metadata, expanded) = process_derive_schema(&input);
assert_eq!(metadata.name, "User");
assert!(metadata.definition.contains("struct User"));
let code = expanded.to_string();
assert!(code.contains("SchemaBuilder"));
assert!(code.contains("User"));
}
#[test]
fn test_process_derive_schema_enum() {
let input: syn::DeriveInput = syn::parse_quote! {
enum Status {
Active,
Inactive,
}
};
let (metadata, expanded) = process_derive_schema(&input);
assert_eq!(metadata.name, "Status");
assert!(metadata.definition.contains("enum Status"));
let code = expanded.to_string();
assert!(code.contains("SchemaBuilder"));
}
#[test]
fn test_process_derive_schema_generic() {
let input: syn::DeriveInput = syn::parse_quote! {
struct Container<T> {
value: T,
}
};
let (metadata, expanded) = process_derive_schema(&input);
assert_eq!(metadata.name, "Container");
let code = expanded.to_string();
assert!(code.contains("SchemaBuilder"));
assert!(code.contains("impl"));
}
#[test]
fn test_extract_schema_name_attr_with_name() {
let attrs: Vec<syn::Attribute> = syn::parse_quote! {
#[schema(name = "CustomName")]
};
let result = extract_schema_name_attr(&attrs);
assert_eq!(result, Some("CustomName".to_string()));
}
#[test]
fn test_extract_schema_name_attr_without_name() {
let attrs: Vec<syn::Attribute> = syn::parse_quote! {
#[derive(Debug)]
};
let result = extract_schema_name_attr(&attrs);
assert_eq!(result, None);
}
#[test]
fn test_extract_schema_name_attr_empty_schema() {
let attrs: Vec<syn::Attribute> = syn::parse_quote! {
#[schema]
};
let result = extract_schema_name_attr(&attrs);
assert_eq!(result, None);
}
#[test]
fn test_extract_schema_name_attr_with_other_attrs() {
let attrs: Vec<syn::Attribute> = syn::parse_quote! {
#[derive(Clone)]
#[schema(name = "MySchema")]
#[serde(rename_all = "camelCase")]
};
let result = extract_schema_name_attr(&attrs);
assert_eq!(result, Some("MySchema".to_string()));
}
#[test]
fn test_process_derive_schema_simple() {
let input: syn::DeriveInput = syn::parse_quote! {
struct User {
id: i32,
name: String,
}
};
let (metadata, tokens) = process_derive_schema(&input);
assert_eq!(metadata.name, "User");
assert!(metadata.definition.contains("User"));
let tokens_str = tokens.to_string();
assert!(tokens_str.contains("SchemaBuilder"));
}
#[test]
fn test_process_derive_schema_with_custom_name() {
let input: syn::DeriveInput = syn::parse_quote! {
#[schema(name = "CustomUserSchema")]
struct User {
id: i32,
}
};
let (metadata, _) = process_derive_schema(&input);
assert_eq!(metadata.name, "CustomUserSchema");
}
#[test]
fn test_process_derive_schema_with_generics() {
let input: syn::DeriveInput = syn::parse_quote! {
struct Container<T> {
value: T,
}
};
let (metadata, tokens) = process_derive_schema(&input);
assert_eq!(metadata.name, "Container");
let tokens_str = tokens.to_string();
assert!(tokens_str.contains("< T >") || tokens_str.contains("<T>"));
}
}