mxr-search 0.4.2

Tantivy-based full-text search for mxr
Documentation
use mxr_core::id::SavedSearchId;
use mxr_core::types::SavedSearch;
use mxr_store::Store;
use std::sync::Arc;

pub struct SavedSearchService {
    store: Arc<Store>,
}

impl SavedSearchService {
    pub fn new(store: Arc<Store>) -> Self {
        Self { store }
    }

    pub async fn create(&self, search: &SavedSearch) -> Result<(), mxr_core::MxrError> {
        self.store
            .insert_saved_search(search)
            .await
            .map_err(|e| mxr_core::MxrError::Store(e.to_string()))
    }

    pub async fn list(&self) -> Result<Vec<SavedSearch>, mxr_core::MxrError> {
        self.store
            .list_saved_searches()
            .await
            .map_err(|e| mxr_core::MxrError::Store(e.to_string()))
    }

    pub async fn delete(&self, id: &SavedSearchId) -> Result<(), mxr_core::MxrError> {
        self.store
            .delete_saved_search(id)
            .await
            .map_err(|e| mxr_core::MxrError::Store(e.to_string()))
    }

    pub async fn get_by_name(&self, name: &str) -> Result<Option<SavedSearch>, mxr_core::MxrError> {
        let searches = self.list().await?;
        Ok(searches.into_iter().find(|s| s.name == name))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use mxr_core::types::SortOrder;
    use mxr_core::SearchMode;

    fn make_saved_search(name: &str, query: &str) -> SavedSearch {
        SavedSearch {
            id: SavedSearchId::new(),
            account_id: None,
            name: name.to_string(),
            query: query.to_string(),
            search_mode: SearchMode::Lexical,
            sort: SortOrder::DateDesc,
            icon: None,
            position: 0,
            created_at: chrono::Utc::now(),
        }
    }

    #[tokio::test]
    async fn saved_create_list_delete() {
        let store = Arc::new(Store::in_memory().await.unwrap());
        let svc = SavedSearchService::new(store);

        let search = make_saved_search("Unread from Alice", "from:alice is:unread");
        svc.create(&search).await.unwrap();

        let list = svc.list().await.unwrap();
        assert_eq!(list.len(), 1);
        assert_eq!(list[0].name, "Unread from Alice");

        svc.delete(&search.id).await.unwrap();
        let list = svc.list().await.unwrap();
        assert_eq!(list.len(), 0);
    }

    #[tokio::test]
    async fn saved_get_by_name() {
        let store = Arc::new(Store::in_memory().await.unwrap());
        let svc = SavedSearchService::new(store);

        let search = make_saved_search("Important", "is:starred");
        svc.create(&search).await.unwrap();

        let found = svc.get_by_name("Important").await.unwrap();
        assert!(found.is_some());
        assert_eq!(found.unwrap().query, "is:starred");

        let not_found = svc.get_by_name("Nonexistent").await.unwrap();
        assert!(not_found.is_none());
    }

    #[tokio::test]
    async fn saved_duplicate_names_allowed() {
        let store = Arc::new(Store::in_memory().await.unwrap());
        let svc = SavedSearchService::new(store);

        let s1 = make_saved_search("Inbox", "label:inbox");
        let s2 = make_saved_search("Inbox", "label:inbox is:unread");
        svc.create(&s1).await.unwrap();
        svc.create(&s2).await.unwrap();

        let list = svc.list().await.unwrap();
        assert_eq!(list.len(), 2);
    }
}