typesensei 0.2.0

Typesense client library
Documentation
use crate::{Client, Error};
use base64::Engine;
use hmac::{Hmac, Mac};
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
use sha2::Sha256;
use std::{
    iter::once,
    time::{Duration, SystemTime},
};
use tracing::instrument;

const PATH: &'static str = "keys";

#[skip_serializing_none]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiKey {
    pub description: String,
    pub actions: Vec<String>,
    pub collections: Vec<String>,
    pub value: Option<String>,
    pub expires_at: Option<u64>,
}

#[derive(Debug, Clone, Copy)]
pub struct Keys<'a> {
    client: &'a Client,
}

impl<'a> Keys<'a> {
    pub(crate) fn new(client: &'a Client) -> Keys<'a> {
        Self { client }
    }

    #[instrument]
    pub async fn create(&self, key: &ApiKey) -> Result<ApiKey, Error> {
        self.client.post((key, once(PATH))).await
    }

    #[instrument]
    pub async fn retreive(&self, id: &str) -> Result<ApiKeyResponse, Error> {
        self.client.get([PATH, id].into_iter()).await
    }

    #[instrument]
    pub async fn retreive_all(&self) -> Result<ApiKeyResponses, Error> {
        self.client.get(once(PATH)).await
    }

    #[instrument]
    pub async fn delete(&self, id: &str) -> Result<ApiKeyDeleteResponse, Error> {
        self.client.delete([PATH, id].into_iter()).await
    }
}

pub fn generate_scoped_search_key(
    key: impl AsRef<str>,
    expire_in: Duration,
) -> GenerateScopedSearchKeyBuilder {
    GenerateScopedSearchKeyBuilder {
        key: key.as_ref().to_owned(),
        ..Default::default()
    }
    .expire_in(expire_in)
}

#[derive(Debug, Default, Clone)]
pub struct GenerateScopedSearchKeyBuilder {
    key: String,
    filters: ScopedSearchFilters,
}

#[derive(Debug, Default, Clone, Serialize)]
#[skip_serializing_none]
pub struct ScopedSearchFilters {
    query_by: Option<String>,
    filter_by: Option<String>,
    exclude_fields: Option<String>,
    include_fields: Option<String>,
    limit_hits: Option<usize>,
    expires_at: u64,
}

impl GenerateScopedSearchKeyBuilder {
    pub fn expire_in(mut self, expire_in: Duration) -> Self {
        let now = SystemTime::now()
            .duration_since(SystemTime::UNIX_EPOCH)
            .unwrap();
        let expire_at = now + expire_in;
        self.filters.expires_at = expire_at.as_secs();

        self
    }

    pub fn query_by(mut self, query_by: String) -> Self {
        self.filters.query_by.replace(query_by);

        self
    }

    pub fn filter_by(mut self, filter_by: String) -> Self {
        self.filters.filter_by.replace(filter_by);

        self
    }

    pub fn limit_hits(mut self, limit_hits: usize) -> Self {
        self.filters.limit_hits.replace(limit_hits);

        self
    }

    pub fn include_fields(mut self, fields: String) -> Self {
        self.filters.include_fields.replace(fields);

        self
    }

    pub fn exclude_fields(mut self, fields: String) -> Self {
        self.filters.exclude_fields.replace(fields);

        self
    }

    /// Returns generated scoped search key and its expiration time in seconds
    pub fn build(self) -> Result<(String, u64), Error> {
        let expires_at = self.filters.expires_at;
        let params = serde_json::to_string(&self.filters).unwrap();

        let mut mac = Hmac::<Sha256>::new_from_slice(self.key.as_bytes()).unwrap();
        mac.update(params.as_bytes());
        let result = mac.finalize();

        let standard = base64::engine::general_purpose::STANDARD;
        let digest = standard.encode(result.into_bytes());

        let key_prefix = &self.key.as_str()[0..4];
        let raw_scoped_key = format!("{}{}{}", digest, key_prefix, params);

        Ok((standard.encode(raw_scoped_key.as_bytes()), expires_at))
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiKeyDeleteResponse {
    pub id: usize,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiKeyResponses {
    keys: Vec<ApiKeyResponse>,
}

#[skip_serializing_none]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiKeyResponse {
    pub id: usize,
    pub description: String,
    pub actions: Vec<String>,
    pub collections: Vec<String>,
    pub value_prefix: String,
}