typesense 0.4.0

Client for typesense
Documentation
//! Provides access to the document, search, and override-related API endpoints.
//!
//! An instance of `Documents` is scoped to a specific collection and is created
//! via the main `client.collection_schemaless("collection_name").documents()` method or
//! `client.collection_named::<T>("...").documents()`.

use crate::{
    Client, Error, execute_wrapper,
    models::{DocumentIndexParameters, SearchResult},
    traits,
};
use ::std::borrow::Cow;
use serde::{Serialize, de::DeserializeOwned};
use typesense_codegen::{
    apis::documents_api,
    models::{
        self as raw_models, DeleteDocumentsParameters, ExportDocumentsParameters,
        ImportDocumentsParameters, UpdateDocumentsParameters,
    },
};
/// Provides methods for interacting with documents within a specific Typesense collection.
///
/// This struct is generic over the document type `D`. If created via `client.collection_schemaless(...)`,
/// `D` defaults to `serde_json::Value`. If created via `client.collection_named::<MyType>(...)`,
/// `D` will be `MyType`.
pub struct Documents<'d, D = serde_json::Value>
where
    D: DeserializeOwned + Serialize,
{
    client: &'d Client,
    collection_name: &'d str,
    _phantom: core::marker::PhantomData<D>,
}

impl<'d, D> Documents<'d, D>
where
    D: DeserializeOwned + Serialize,
{
    /// Creates a new `Documents` instance.
    #[inline]
    pub(super) fn new(client: &'d Client, collection_name: &'d str) -> Self {
        Self {
            client,
            collection_name,
            _phantom: core::marker::PhantomData,
        }
    }

    /// Indexes a document in the collection.
    /// # Arguments
    /// * `document` - A `serde_json::Value` representing the document.
    /// * `action` - The indexing action to perform (e.g., "create", "upsert").
    async fn index(
        &self,
        document: serde_json::Value,
        action: &str,
        params: Option<DocumentIndexParameters>,
    ) -> Result<serde_json::Value, Error<documents_api::IndexDocumentError>> {
        let params = documents_api::IndexDocumentParams {
            collection_name: self.collection_name.into(),
            body: document,
            action: Some(action.into()),
            dirty_values: params.and_then(|d| d.dirty_values), // Or expose this as an argument if needed
        };
        execute_wrapper!(self, documents_api::index_document, params)
    }

    // --- Bulk Operation Methods ---

    /// Imports a batch of documents in JSONL format.
    ///
    /// The documents to be imported must be formatted as a newline-delimited JSON string.
    ///
    /// # Arguments
    /// * `documents_jsonl` - A string containing the documents in JSONL format.
    /// * `params` - An `ImportDocumentsParameters` struct containing options like `action` and `batch_size`.
    pub async fn import_jsonl(
        &self,
        documents_jsonl: impl Into<Cow<'_, str>>,
        params: ImportDocumentsParameters,
    ) -> Result<String, Error<documents_api::ImportDocumentsError>> {
        let params = documents_api::ImportDocumentsParams {
            body: documents_jsonl.into(),
            collection_name: self.collection_name.into(),

            action: params.action,
            batch_size: params.batch_size,
            dirty_values: params.dirty_values,
            remote_embedding_batch_size: params.remote_embedding_batch_size,
            return_doc: params.return_doc,
            return_id: params.return_id,
        };
        execute_wrapper!(self, documents_api::import_documents, params)
    }

    /// Exports all documents in a collection in JSONL format.
    ///
    /// # Arguments
    /// * `params` - An `ExportDocumentsParameters` struct containing options like `filter_by` and `include_fields`.
    pub async fn export_jsonl(
        &self,
        params: ExportDocumentsParameters<'_>,
    ) -> Result<String, Error<documents_api::ExportDocumentsError>> {
        let params = documents_api::ExportDocumentsParams {
            collection_name: self.collection_name.into(),
            exclude_fields: params.exclude_fields,
            filter_by: params.filter_by,
            include_fields: params.include_fields,
        };
        execute_wrapper!(self, documents_api::export_documents, params)
    }

    /// Deletes a batch of documents matching a specific filter condition.
    ///
    /// # Arguments
    /// * `params` - A `DeleteDocumentsParameters` describing the conditions for deleting documents.
    pub async fn delete(
        &self,
        params: DeleteDocumentsParameters<'_>,
    ) -> Result<raw_models::DeleteDocuments200Response, Error<documents_api::DeleteDocumentsError>>
    {
        let params = documents_api::DeleteDocumentsParams {
            collection_name: self.collection_name.into(),
            filter_by: Some(params.filter_by),
            batch_size: params.batch_size,
            ignore_not_found: params.ignore_not_found,
            truncate: params.truncate,
        };
        execute_wrapper!(self, documents_api::delete_documents, params)
    }

    /// Searches for documents in the collection that match the given criteria.
    /// The search results will have their `document` field deserialized into type `D`.
    ///
    /// # Arguments
    /// * `params` - A `SearchParameters` struct containing all search parameters.
    pub async fn search(
        &self,
        params: raw_models::SearchParameters<'_>,
    ) -> Result<SearchResult<D>, Error<documents_api::SearchCollectionError>> {
        let search_params = documents_api::SearchCollectionParams {
            collection_name: self.collection_name.into(),

            // Map all corresponding fields directly.
            cache_ttl: params.cache_ttl,
            conversation: params.conversation,
            conversation_id: params.conversation_id,
            conversation_model_id: params.conversation_model_id,
            drop_tokens_mode: params.drop_tokens_mode,
            drop_tokens_threshold: params.drop_tokens_threshold,
            enable_highlight_v1: params.enable_highlight_v1,
            enable_curations: params.enable_curations,
            enable_synonyms: params.enable_synonyms,
            enable_typos_for_alpha_numerical_tokens: params.enable_typos_for_alpha_numerical_tokens,
            enable_typos_for_numerical_tokens: params.enable_typos_for_numerical_tokens,
            exclude_fields: params.exclude_fields,
            exhaustive_search: params.exhaustive_search,
            facet_by: params.facet_by,
            facet_query: params.facet_query,
            facet_return_parent: params.facet_return_parent,
            facet_strategy: params.facet_strategy,
            filter_by: params.filter_by,
            filter_curated_hits: params.filter_curated_hits,
            group_by: params.group_by,
            group_limit: params.group_limit,
            group_missing_values: params.group_missing_values,
            hidden_hits: params.hidden_hits,
            highlight_affix_num_tokens: params.highlight_affix_num_tokens,
            highlight_end_tag: params.highlight_end_tag,
            highlight_fields: params.highlight_fields,
            highlight_full_fields: params.highlight_full_fields,
            highlight_start_tag: params.highlight_start_tag,
            include_fields: params.include_fields,
            infix: params.infix,
            limit: params.limit,
            max_candidates: params.max_candidates,
            max_extra_prefix: params.max_extra_prefix,
            max_extra_suffix: params.max_extra_suffix,
            max_facet_values: params.max_facet_values,
            max_filter_by_candidates: params.max_filter_by_candidates,
            min_len_1typo: params.min_len_1typo,
            min_len_2typo: params.min_len_2typo,
            num_typos: params.num_typos,
            offset: params.offset,
            curation_tags: params.curation_tags,
            page: params.page,
            per_page: params.per_page,
            pinned_hits: params.pinned_hits,
            pre_segmented_query: params.pre_segmented_query,
            prefix: params.prefix,
            preset: params.preset,
            prioritize_exact_match: params.prioritize_exact_match,
            prioritize_num_matching_fields: params.prioritize_num_matching_fields,
            prioritize_token_position: params.prioritize_token_position,
            q: params.q,
            query_by: params.query_by,
            query_by_weights: params.query_by_weights,
            remote_embedding_num_tries: params.remote_embedding_num_tries,
            remote_embedding_timeout_ms: params.remote_embedding_timeout_ms,
            search_cutoff_ms: params.search_cutoff_ms,
            snippet_threshold: params.snippet_threshold,
            sort_by: params.sort_by,
            split_join_tokens: params.split_join_tokens,
            stopwords: params.stopwords,
            synonym_num_typos: params.synonym_num_typos,
            synonym_prefix: params.synonym_prefix,
            text_match_type: params.text_match_type,
            typo_tokens_threshold: params.typo_tokens_threshold,
            use_cache: params.use_cache,
            vector_query: params.vector_query,
            voice_query: params.voice_query,
            nl_model_id: params.nl_model_id,
            nl_query: params.nl_query,
            enable_analytics: params.enable_analytics,
            synonym_sets: params.synonym_sets,
        };
        execute_wrapper!(self, documents_api::search_collection, search_params)
    }
}

impl<'d, D> Documents<'d, D>
where
    D: traits::Document,
{
    /// Creates a new document in the collection.
    ///
    /// Fails if a document with the same ID already exists. If the document has an `id` field
    /// of type `string`, it will be used as the document's ID. Otherwise, Typesense will
    /// auto-generate an ID. The newly indexed document is returned.
    ///
    /// # Arguments
    /// * `document` - A document struct to create.
    /// * `params` - Optional parameters like `dirty_values`.
    pub async fn create(
        &self,
        document: &D,
        params: Option<DocumentIndexParameters>,
    ) -> Result<D, Error<documents_api::IndexDocumentError>> {
        let doc_value = serde_json::to_value(document)?;
        let result_value = self.index(doc_value, "create", params).await?;
        serde_json::from_value(result_value).map_err(Error::from)
    }

    /// Creates a new document or updates an existing one if an ID match is found.
    ///
    /// This method requires the full document to be sent. For partial updates, use
    /// `collection().document("...").update()`. The indexed document is returned.
    ///
    /// # Arguments
    /// * `document` - A document struct to upsert.
    /// * `params` - Optional parameters like `dirty_values`.
    pub async fn upsert(
        &self,
        document: &D,
        params: Option<DocumentIndexParameters>,
    ) -> Result<D, Error<documents_api::IndexDocumentError>> {
        let doc_value = serde_json::to_value(document)?;
        let result_value = self.index(doc_value, "upsert", params).await?;
        serde_json::from_value(result_value).map_err(Error::from)
    }

    /// Updates a batch of documents matching a specific filter condition.
    ///
    /// # Arguments
    /// * `document` - A struct containing the fields to update.
    /// * `params` - A `UpdateDocumentsParameters` describing the conditions for updating documents.
    pub async fn update(
        &self,
        document: &D::Partial,
        params: UpdateDocumentsParameters<'_>,
    ) -> Result<raw_models::UpdateDocuments200Response, Error<documents_api::UpdateDocumentsError>>
    {
        let params = documents_api::UpdateDocumentsParams {
            collection_name: self.collection_name.into(),
            filter_by: params.filter_by,
            body: document,
        };
        execute_wrapper!(self, documents_api::update_documents, params)
    }
}