use darling::FromMeta;
use text_search_core::IndexType;
#[derive(Debug)]
pub struct DeriveFieldInfo {
pub info: text_search_core::FieldInfo,
pub type_path: syn::TypePath,
}
#[derive(Debug, Default, FromMeta)]
pub struct TextSearchAttr {
#[darling(default)]
pub id: bool,
#[darling(default)]
pub indexed_string: bool,
#[darling(default)]
pub indexed_text: bool,
#[darling(default)]
pub indexed: bool,
#[darling(default)]
pub not_indexed: bool,
#[darling(default)]
pub stored: bool,
#[darling(default)]
pub not_stored: bool,
}
impl TextSearchAttr {
fn into_field_info(
self,
field_name: String,
) -> Result<text_search_core::FieldInfo, darling::Error> {
let index_flags: Vec<(&str, IndexType)> = [
("indexed_string", IndexType::indexed_string),
("indexed_text", IndexType::indexed_text),
("indexed", IndexType::indexed),
("not_indexed", IndexType::not_indexed),
]
.into_iter()
.filter(|(name, _)| match *name {
"indexed_string" => self.indexed_string,
"indexed_text" => self.indexed_text,
"indexed" => self.indexed,
"not_indexed" => self.not_indexed,
_ => false,
})
.collect();
if index_flags.len() > 1 {
return Err(darling::Error::custom(
"Cannot specify multiple index types for the same field",
));
}
let index_type = index_flags.into_iter().next().map(|(_, it)| it);
if self.stored && self.not_stored {
return Err(darling::Error::custom(
"Cannot specify both `stored` and `not_stored` for the same field",
));
}
let stored = if self.not_stored {
Some(false)
} else if self.stored {
Some(true)
} else {
None
};
if self.id && (index_type.is_some() || stored.is_some()) {
return Err(darling::Error::custom(
"Cannot have other attributes when field has `id` attribute",
));
}
if self.id {
Ok(text_search_core::FieldInfo::new_id_field(field_name))
} else {
Ok(text_search_core::FieldInfo::new(
field_name,
index_type,
stored.unwrap_or(true),
))
}
}
}
pub fn parse_field_info(field: &syn::Field) -> Result<DeriveFieldInfo, syn::Error> {
let type_path = if let syn::Type::Path(tp) = &field.ty {
tp.clone()
} else {
syn::TypePath {
qself: None,
path: syn::parse_str("std::marker::PhantomData<u8>").unwrap(),
}
};
let attr = field
.attrs
.iter()
.find(|a| a.path().is_ident("text_search"));
let ts_attr = match attr {
Some(a) => {
let meta = &a.meta;
TextSearchAttr::from_meta(meta).map_err(|e| e.with_span(&a.meta))?
}
None => TextSearchAttr::default(),
};
let field_name = field
.ident
.as_ref()
.ok_or_else(|| syn::Error::new_spanned(field, "Field must have a name"))?
.to_string();
let info = ts_attr
.into_field_info(field_name)
.map_err(|e| e.with_span(field))?;
Ok(DeriveFieldInfo { info, type_path })
}
fn extract_vec_inner_type(type_path: &syn::TypePath) -> Option<syn::TypePath> {
let segment = type_path.path.segments.last()?;
if segment.ident != "Vec" {
return None;
}
if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
if let Some(syn::GenericArgument::Type(syn::Type::Path(inner))) = args.args.first() {
return Some(inner.clone());
}
}
None
}
pub fn get_field_type_path(field: &DeriveFieldInfo) -> syn::TypePath {
if let Some(inner) = extract_vec_inner_type(&field.type_path) {
inner
} else {
field.type_path.clone()
}
}
pub fn is_vec_field(field: &DeriveFieldInfo) -> bool {
extract_vec_inner_type(&field.type_path).is_some()
}