text-search-derive 0.1.5

Derive macro for text-search crate
Documentation
use crate::context::Ctxt;

use quote::quote;
use syn::{Expr, Field, Type, parse_str};
use text_search_core::{FieldInfo, FieldType, IndexType, symbol::*};

pub fn get_field_info(ctxt: &Ctxt, field: &Field) -> FieldInfo {
    let mut is_id: bool = false;
    let mut index_type: Option<IndexType> = None;
    let mut stored: Option<bool> = None;
    let field_type: FieldType = if let Type::Path(type_path) = &field.ty {
        if let Some(segment) = type_path.path.segments.last() {
            FieldType::get_field_type(segment.ident.to_string().as_str())
        } else {
            FieldType::Unhandled
        }
    } else {
        FieldType::Unhandled
    };

    for attr in &field.attrs {
        if attr.path() != TEXT_SEARCH {
            continue;
        }

        if let syn::Meta::List(meta) = &attr.meta {
            if meta.tokens.is_empty() {
                continue;
            }
        }

        if let Err(err) = attr.parse_nested_meta(|meta| {
            if meta.path == ID {
                is_id = true;
            }

            let _index_type = if meta.path == INDEXED_STRING {
                Some(IndexType::indexed_string)
            } else if meta.path == INDEXED_TEXT {
                Some(IndexType::indexed_text)
            } else if meta.path == NOT_INDEXED {
                Some(IndexType::not_indexed)
            } else {
                None
            };

            let _stored = if meta.path == STORED {
                Some(true)
            } else if meta.path == NOT_STORED {
                Some(false)
            } else {
                None
            };

            if index_type.is_some() && _index_type.is_some() {
                panic!(
                    "Cannot have {:?} and {:?} together",
                    index_type.clone().unwrap(),
                    _index_type.unwrap()
                );
            } else if index_type.is_none() {
                index_type = _index_type;
            }

            if stored.is_some() && _stored.is_some() {
                panic!(
                    "Cannot have {:?} and {:?} together",
                    stored.clone().unwrap(),
                    _stored.unwrap()
                );
            } else if stored.is_none() {
                stored = _stored;
            }

            if is_id && (index_type.is_some() || stored.is_some()) {
                panic!("Cannot have other attributes when field has id attribute.")
            }

            Ok(())
        }) {
            ctxt.syn_error(err);
        }
    }

    let field_name = field.ident.as_ref().unwrap().to_string();
    if is_id {
        FieldInfo::new_id_field(field_name, field_type)
    } else {
        FieldInfo::new(field_name, field_type, index_type, stored.unwrap_or(true))
    }
}

pub fn generate_field_info_token(field_info: &FieldInfo) -> proc_macro2::TokenStream {
    let index_type_token = match field_info.index_type {
        IndexType::indexed_string => quote! {text_search::IndexType::indexed_string},
        IndexType::indexed_text => quote! {text_search::IndexType::indexed_text},
        IndexType::indexed => quote! {text_search::IndexType::indexed},
        IndexType::not_indexed => quote! {text_search::IndexType::not_indexed},
    };

    let stored_token = match field_info.stored {
        true => quote! {true},
        false => quote! {false},
    };

    let field_type_token = match field_info.field_type {
        FieldType::String => quote! {text_search::FieldType::String},
        FieldType::I32 => quote! {text_search::FieldType::I32},
        FieldType::VecString => quote! {text_search::FieldType::VecString},
        FieldType::Unhandled => quote! {text_search::FieldType::Unhandled},
    };

    let field_name = format!("{}", field_info.field_name);

    quote! {text_search::FieldInfo::new(#field_name.into(), #field_type_token, Some(#index_type_token), #stored_token),}
}

pub fn generate_field_info_to_document(field_info: &FieldInfo) -> proc_macro2::TokenStream {
    let field_name = parse_str::<Expr>(&field_info.field_name).unwrap();
    let field_name_string = format!("{}", field_info.field_name);
    match field_info.field_type {
        FieldType::String => quote! {
            let #field_name = schema.get_field(#field_name_string).unwrap();
            doc.add_text(#field_name, &self.#field_name);
        },
        FieldType::VecString => quote! {
            let #field_name = schema.get_field(#field_name_string).unwrap();
            for v in &self.#field_name {
                doc.add_text(#field_name, v);
            }
        },
        FieldType::I32 => quote! {
            let #field_name = schema.get_field(#field_name_string).unwrap();
            doc.add_i64(#field_name, self.#field_name as i64);
        },
        FieldType::Unhandled => panic!("Unhandled field type."),
    }
}

pub fn generate_field_info_temp_var_assignments(
    field_info: &FieldInfo,
) -> proc_macro2::TokenStream {
    let field_name = &field_info.field_name;
    let field_name_value = format!("{}", field_name);
    let field = parse_str::<Expr>(&field_name_value).unwrap();
    let field_id_var = parse_str::<Expr>((field_name.to_owned() + "_field_id").as_str()).unwrap();
    let field_owned_value_var =
        parse_str::<Expr>((field_name.to_owned() + "_owned_value").as_str()).unwrap();
    let field_value_var = parse_str::<Expr>((field_name.to_owned() + "_value").as_str()).unwrap();
    let field_value_assignment = match field_info.field_type {
        FieldType::String => {
            quote! {
                if let text_search::tantivy::schema::OwnedValue::Str(s) = #field_owned_value_var
                {
                    s
                } else {
                    Default::default()
                };
            }
        }
        FieldType::VecString => {
            quote! {
                doc
                    .get_all(#field)
                    .filter_map(|value|tantivy::schema::Value::as_str(&value))
                    .map(|s| s.to_string())
                    .collect();
            }
        }
        FieldType::I32 => {
            quote! {
                if let text_search::tantivy::schema::OwnedValue::I64(i) = #field_owned_value_var
                {
                    i as i32
                } else {
                    Default::default()
                };
            }
        }
        FieldType::Unhandled => panic!("Unhandled field type."),
    };
    quote! {
        let #field =  schema.get_field(#field_name_value).unwrap();
        let #field_id_var =  #field.field_id();
        let #field_owned_value_var = doc.field_values().into_iter().filter(|x| x.field.field_id() == #field_id_var).next().unwrap().value.clone();
        let #field_value_var = #field_value_assignment
    }
}

pub fn generate_term_initialisation(
    field_info: &FieldInfo,
    include_self: bool,
) -> proc_macro2::TokenStream {
    let field_name = parse_str::<Expr>(&{
        if include_self {
            let value = format!("self.{}", field_info.field_name.clone());
            value
        } else {
            field_info.field_name.clone()
        }
    })
    .unwrap();
    return match field_info.field_type {
        FieldType::String => quote! {
            return text_search::tantivy::Term::from_field_text(field, &#field_name);
        },
        FieldType::I32 => quote! {
            return text_search::tantivy::Term::from_field_i64(field, #field_name as i64);
        },
        FieldType::Unhandled => panic!("Unhandled field type."),
        _ => {
            panic!("Cannot generate term initialization.");
        }
    };
}