use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC};
use std::time::Duration;
use reqwest::Method;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use crate::client::Client;
use crate::error::Result;
use crate::types::{FeedItem, FeedReplaceResult};
const EXTERNAL_ID_SET: &AsciiSet = &NON_ALPHANUMERIC
.remove(b'-')
.remove(b'_')
.remove(b'.')
.remove(b'~');
fn encode_external_id(external_id: &str) -> String {
utf8_percent_encode(external_id, EXTERNAL_ID_SET).to_string()
}
#[derive(Debug, Clone, Serialize)]
pub struct FeedItemInput<V> {
pub external_id: String,
pub data: V,
#[serde(
skip_serializing_if = "Option::is_none",
serialize_with = "serialize_ttl_as_secs"
)]
pub ttl: Option<Duration>,
}
impl<V> FeedItemInput<V> {
pub fn new(external_id: impl Into<String>, data: V, ttl: Option<Duration>) -> Self {
Self {
external_id: external_id.into(),
data,
ttl,
}
}
}
fn serialize_ttl_as_secs<S: serde::Serializer>(
ttl: &Option<Duration>,
serializer: S,
) -> std::result::Result<S::Ok, S::Error> {
match ttl {
Some(ttl) => serializer.serialize_u64(ttl.as_secs()),
None => serializer.serialize_none(),
}
}
#[derive(Clone)]
pub struct Feed {
client: Client,
slug: String,
}
impl Feed {
pub(crate) fn new(client: Client, slug: String) -> Self {
Self { client, slug }
}
pub async fn set<V: Serialize>(
&self,
external_id: &str,
data: &V,
ttl: Option<Duration>,
) -> Result<FeedItem<serde_json::Value>> {
let env_id = &self.client.inner.env_id;
let slug = &self.slug;
let mut body = serde_json::json!({ "data": data });
if let Some(ttl) = ttl {
body["ttl"] = serde_json::json!(ttl.as_secs());
}
self.client
.inner
.http
.request(
Method::PUT,
&format!(
"/c/{env_id}/feeds/{slug}/items/{}",
encode_external_id(external_id)
),
Some(&body),
)
.await
}
pub async fn replace<V: Serialize>(
&self,
items: &[FeedItemInput<V>],
) -> Result<FeedReplaceResult> {
let env_id = &self.client.inner.env_id;
let slug = &self.slug;
let body = serde_json::json!({ "items": items });
self.client
.inner
.http
.request(
Method::PUT,
&format!("/c/{env_id}/feeds/{slug}/items"),
Some(&body),
)
.await
}
pub async fn list<T: DeserializeOwned>(&self) -> Result<Vec<FeedItem<T>>> {
#[derive(Deserialize)]
struct Wrapper<T> {
items: Vec<FeedItem<T>>,
}
let env_id = &self.client.inner.env_id;
let slug = &self.slug;
let resp: Wrapper<T> = self
.client
.inner
.http
.request(
Method::GET,
&format!("/c/{env_id}/feeds/{slug}/items"),
None::<&()>,
)
.await?;
Ok(resp.items)
}
pub async fn delete(&self, external_id: &str) -> Result<()> {
let env_id = &self.client.inner.env_id;
let slug = &self.slug;
self.client
.inner
.http
.request(
Method::DELETE,
&format!(
"/c/{env_id}/feeds/{slug}/items/{}",
encode_external_id(external_id)
),
None::<&()>,
)
.await
}
}