use reqwest::Method;
use serde::Deserialize;
use crate::client::Client;
use crate::error::Error;
use crate::http::RequestSpec;
pub struct Emoji {
pub(crate) client: Client,
}
#[derive(Debug, Clone, Deserialize)]
#[non_exhaustive]
pub struct EmojiEntry {
#[serde(rename = "c")]
pub character: String,
#[serde(rename = "n")]
pub name: String,
#[serde(rename = "g")]
pub group: String,
#[serde(rename = "gen")]
pub generates: bool,
#[serde(rename = "k", default)]
pub keywords: Option<Vec<String>>,
}
#[derive(Debug, Clone, Deserialize)]
#[non_exhaustive]
pub struct EmojiSet {
pub accept_max_version: f64,
pub generate_max_version: f64,
pub max_graphemes: u64,
pub emoji: Vec<EmojiEntry>,
}
pub(crate) struct CachedSet {
pub(crate) etag: String,
pub(crate) set: EmojiSet,
}
impl Emoji {
pub async fn set(&self) -> Result<EmojiSet, Error> {
let cached_etag = {
let cache = lock(&self.client.emoji_cache);
cache.as_ref().map(|c| c.etag.clone())
};
match cached_etag {
Some(etag) => self.fetch_with_etag(&etag).await,
None => {
let response = self
.client
.transport
.send(RequestSpec::new(Method::GET, "/api/v1/emoji-set"))
.await?;
self.store(response).await
}
}
}
async fn fetch_with_etag(&self, etag: &str) -> Result<EmojiSet, Error> {
let spec = RequestSpec::new(Method::GET, "/api/v1/emoji-set").header("If-None-Match", etag);
let response = self.client.transport.send(spec).await?;
if response.status() == reqwest::StatusCode::NOT_MODIFIED {
{
let cache = lock(&self.client.emoji_cache);
if let Some(cached) = cache.as_ref() {
return Ok(cached.set.clone());
}
}
let fresh = self
.client
.transport
.send(RequestSpec::new(Method::GET, "/api/v1/emoji-set"))
.await?;
return self.store(fresh).await;
}
self.store(response).await
}
async fn store(&self, response: reqwest::Response) -> Result<EmojiSet, Error> {
let etag = response
.headers()
.get("etag")
.and_then(|v| v.to_str().ok())
.map(str::to_owned);
let bytes = response.bytes().await.map_err(Error::Transport)?;
let set: EmojiSet = serde_json::from_slice(&bytes).map_err(Error::Decode)?;
if let Some(etag) = etag {
let mut cache = lock(&self.client.emoji_cache);
*cache = Some(CachedSet {
etag,
set: set.clone(),
});
}
Ok(set)
}
}
fn lock(
cache: &std::sync::Mutex<Option<CachedSet>>,
) -> std::sync::MutexGuard<'_, Option<CachedSet>> {
match cache.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
}
}