branch: main
builder.rs
10167 bytesRaw
use js_sys::{ArrayBuffer, Function, Object, Promise, Uint8Array};
use serde::{de::DeserializeOwned, Serialize};
use serde_json::Value;
use serde_wasm_bindgen::Serializer;
use wasm_bindgen::{JsCast, JsValue};
use wasm_bindgen_futures::JsFuture;

use crate::kv::{KvError, ListResponse};

/// A builder to configure put requests.
#[derive(Debug, Clone)]
#[must_use = "PutOptionsBuilder does nothing until you 'execute' it"]
pub struct PutOptionsBuilder {
    pub(crate) this: Object,
    pub(crate) put_function: Function,
    pub(crate) name: JsValue,
    pub(crate) value: JsValue,
    pub(crate) expiration: Option<u64>,
    pub(crate) expiration_ttl: Option<u64>,
    pub(crate) metadata: Option<Value>,
}

#[derive(Serialize)]
struct PutOptions {
    #[serde(skip_serializing_if = "Option::is_none")]
    expiration: Option<u64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(rename = "expirationTtl")]
    expiration_ttl: Option<u64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    metadata: Option<Value>,
}

impl PutOptionsBuilder {
    /// When (expressed as a [unix timestamp](https://en.wikipedia.org/wiki/Unix_time)) the key
    /// value pair will expire in the store.
    pub fn expiration(mut self, expiration: u64) -> Self {
        self.expiration = Some(expiration);
        self
    }
    /// How many seconds until the key value pair will expire.
    pub fn expiration_ttl(mut self, expiration_ttl: u64) -> Self {
        self.expiration_ttl = Some(expiration_ttl);
        self
    }
    /// Metadata to be stored with the key value pair.
    pub fn metadata<T: Serialize>(mut self, metadata: T) -> Result<Self, KvError> {
        self.metadata = Some(serde_json::to_value(metadata)?);
        Ok(self)
    }
    /// Puts the value in the kv store.
    pub async fn execute(self) -> Result<(), KvError> {
        let ser = Serializer::json_compatible();
        let options_object = PutOptions {
            expiration: self.expiration,
            expiration_ttl: self.expiration_ttl,
            metadata: self.metadata,
        }
        .serialize(&ser)
        .map_err(JsValue::from)?;

        let promise: Promise = self
            .put_function
            .call3(&self.this, &self.name, &self.value, &options_object)?
            .into();
        JsFuture::from(promise)
            .await
            .map(|_| ())
            .map_err(KvError::from)
    }
}

/// A builder to configure list requests.
#[derive(Debug, Clone)]
#[must_use = "ListOptionsBuilder does nothing until you 'execute' it"]
pub struct ListOptionsBuilder {
    pub(crate) this: Object,
    pub(crate) list_function: Function,
    pub(crate) limit: Option<u64>,
    pub(crate) cursor: Option<String>,
    pub(crate) prefix: Option<String>,
}

#[derive(Serialize)]
struct ListOptions {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub(crate) limit: Option<u64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub(crate) cursor: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub(crate) prefix: Option<String>,
}

impl ListOptionsBuilder {
    /// The maximum number of keys returned. The default is 1000, which is the maximum. It is
    /// unlikely that you will want to change this default, but it is included for completeness.
    pub fn limit(mut self, limit: u64) -> Self {
        self.limit = Some(limit);
        self
    }
    /// A string returned by a previous response used to paginate the keys in the store.
    pub fn cursor(mut self, cursor: String) -> Self {
        self.cursor = Some(cursor);
        self
    }
    /// A prefix that all keys must start with for them to be included in the response.
    pub fn prefix(mut self, prefix: String) -> Self {
        self.prefix = Some(prefix);
        self
    }
    /// Lists the key value pairs in the kv store.
    pub async fn execute(self) -> Result<ListResponse, KvError> {
        let ser = Serializer::json_compatible();
        let options_object = ListOptions {
            limit: self.limit,
            cursor: self.cursor,
            prefix: self.prefix,
        }
        .serialize(&ser)
        .map_err(JsValue::from)?;

        let promise: Promise = self
            .list_function
            .call1(&self.this, &options_object)?
            .into();

        let value = JsFuture::from(promise).await?;
        let resp = serde_wasm_bindgen::from_value(value).map_err(JsValue::from)?;
        Ok(resp)
    }
}

/// A builder to configure get requests.
#[derive(Debug, Clone)]
#[must_use = "GetOptionsBuilder does nothing until you 'get' it"]
pub struct GetOptionsBuilder {
    pub(crate) this: Object,
    pub(crate) get_function: Function,
    pub(crate) get_with_meta_function: Function,
    pub(crate) name: JsValue,
    pub(crate) cache_ttl: Option<u64>,
    pub(crate) value_type: Option<GetValueType>,
}

#[derive(Serialize)]
struct GetOptions {
    #[serde(rename = "cacheTtl", skip_serializing_if = "Option::is_none")]
    pub(crate) cache_ttl: Option<u64>,
    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
    pub(crate) value_type: Option<GetValueType>,
}

impl GetOptionsBuilder {
    /// The cache_ttl parameter must be an integer that is greater than or equal to 60. It defines
    /// the length of time in seconds that a KV result is cached in the edge location that it is
    /// accessed from. This can be useful for reducing cold read latency on keys that are read
    /// relatively infrequently. It is especially useful if your data is write-once or
    /// write-rarely, but is not recommended if your data is updated often and you need to see
    /// updates shortly after they're written, because writes that happen from other edge locations
    /// won't be visible until the cached value expires.
    pub fn cache_ttl(mut self, cache_ttl: u64) -> Self {
        self.cache_ttl = Some(cache_ttl);
        self
    }

    fn value_type(mut self, value_type: GetValueType) -> Self {
        self.value_type = Some(value_type);
        self
    }

    fn options(&self) -> Result<JsValue, KvError> {
        let ser = Serializer::json_compatible();
        Ok(GetOptions {
            cache_ttl: self.cache_ttl,
            value_type: self.value_type,
        }
        .serialize(&ser)
        .map_err(JsValue::from)?)
    }

    async fn get(self) -> Result<JsValue, KvError> {
        let options_object = self.options()?;

        let promise: Promise = self
            .get_function
            .call2(&self.this, &self.name, &options_object)?
            .into();
        JsFuture::from(promise).await.map_err(KvError::from)
    }

    /// Gets the value as a string.
    pub async fn text(self) -> Result<Option<String>, KvError> {
        let value = self.value_type(GetValueType::Text).get().await?;
        Ok(value.as_string())
    }

    /// Tries to deserialize the inner text to the generic type.
    pub async fn json<T>(self) -> Result<Option<T>, KvError>
    where
        T: DeserializeOwned,
    {
        let value = self.value_type(GetValueType::Json).get().await?;
        Ok(if value.is_null() {
            None
        } else {
            Some(serde_wasm_bindgen::from_value(value).map_err(JsValue::from)?)
        })
    }

    /// Gets the value as a byte slice.
    pub async fn bytes(self) -> Result<Option<Vec<u8>>, KvError> {
        let v = self.value_type(GetValueType::ArrayBuffer).get().await?;
        if ArrayBuffer::instanceof(&v) {
            let buffer = ArrayBuffer::from(v);
            let buffer = Uint8Array::new(&buffer);
            Ok(Some(buffer.to_vec()))
        } else {
            Ok(None)
        }
    }

    async fn get_with_metadata<M>(&self) -> Result<(JsValue, Option<M>), KvError>
    where
        M: DeserializeOwned,
    {
        let options_object = self.options()?;

        let promise: Promise = self
            .get_with_meta_function
            .call2(&self.this, &self.name, &options_object)?
            .into();

        let pair = JsFuture::from(promise).await?;
        let metadata = crate::kv::get(&pair, "metadata")?;
        let value = crate::kv::get(&pair, "value")?;

        Ok((
            value,
            if metadata.is_null() {
                None
            } else {
                Some(serde_wasm_bindgen::from_value(metadata).map_err(JsValue::from)?)
            },
        ))
    }

    /// Gets the value as a string and it's associated metadata.
    pub async fn text_with_metadata<M>(self) -> Result<(Option<String>, Option<M>), KvError>
    where
        M: DeserializeOwned,
    {
        let (value, metadata) = self
            .value_type(GetValueType::Text)
            .get_with_metadata()
            .await?;
        Ok((value.as_string(), metadata))
    }

    /// Tries to deserialize the inner text to the generic type along with it's metadata.
    pub async fn json_with_metadata<T, M>(self) -> Result<(Option<T>, Option<M>), KvError>
    where
        T: DeserializeOwned,
        M: DeserializeOwned,
    {
        let (value, metadata) = self
            .value_type(GetValueType::Json)
            .get_with_metadata()
            .await?;
        Ok((
            if value.is_null() {
                None
            } else {
                Some(serde_wasm_bindgen::from_value(value).map_err(JsValue::from)?)
            },
            metadata,
        ))
    }

    /// Gets the value as a byte slice and it's associated metadata.
    pub async fn bytes_with_metadata<M>(self) -> Result<(Option<Vec<u8>>, Option<M>), KvError>
    where
        M: DeserializeOwned,
    {
        let (value, metadata) = self
            .value_type(GetValueType::ArrayBuffer)
            .get_with_metadata()
            .await?;

        if ArrayBuffer::instanceof(&value) {
            let buffer = ArrayBuffer::from(value);
            let buffer = Uint8Array::new(&buffer);
            Ok((Some(buffer.to_vec()), metadata))
        } else {
            Ok((None, metadata))
        }
    }
}

#[derive(Debug, Clone, Serialize, Copy)]
#[serde(rename_all = "camelCase")]
pub(crate) enum GetValueType {
    Text,
    ArrayBuffer,
    Json,
}