use serde::{Deserialize, Serialize, Serializer};
use crate::error::HttpError;
use crate::http::client::HttpClient;
use crate::http::routing::Route;
use crate::model::id::{AttachmentId, ChannelId, MessageId, UserId};
impl HttpClient {
pub async fn create_meme_from_message(
&self,
channel_id: ChannelId,
message_id: MessageId,
params: &CreateFavoriteMemeFromMessage,
) -> Result<FavoriteMeme, HttpError> {
self.request(
Route::CreateMemeFromMessage {
channel_id,
message_id,
},
Some(params),
)
.await
}
pub async fn list_favorite_memes(&self) -> Result<Vec<FavoriteMeme>, HttpError> {
self.request_no_body(Route::ListFavoriteMemes).await
}
pub async fn create_meme_from_url(
&self,
params: &CreateFavoriteMemeFromUrl,
) -> Result<FavoriteMeme, HttpError> {
self.request(Route::CreateMemeFromUrl, Some(params)).await
}
pub async fn get_favorite_meme(&self, meme_id: &str) -> Result<FavoriteMeme, HttpError> {
self.request_no_body(Route::GetFavoriteMeme {
meme_id: meme_id.into(),
})
.await
}
pub async fn update_favorite_meme(
&self,
meme_id: &str,
params: &UpdateFavoriteMeme,
) -> Result<FavoriteMeme, HttpError> {
self.request(
Route::UpdateFavoriteMeme {
meme_id: meme_id.into(),
},
Some(params),
)
.await
}
pub async fn delete_favorite_meme(&self, meme_id: &str) -> Result<(), HttpError> {
self.request_empty(
Route::DeleteFavoriteMeme {
meme_id: meme_id.into(),
},
None::<&()>,
)
.await
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct FavoriteMeme {
pub id: String,
pub user_id: UserId,
pub name: String,
pub alt_text: Option<String>,
pub tags: Vec<String>,
pub attachment_id: AttachmentId,
pub filename: String,
pub content_type: String,
pub content_hash: Option<String>,
pub size: u64,
pub width: Option<u64>,
pub height: Option<u64>,
pub duration: Option<f64>,
pub url: String,
pub is_gifv: Option<bool>,
pub klipy_slug: Option<String>,
pub tenor_slug_id: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct CreateFavoriteMemeFromMessage {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub alt_text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub attachment_id: Option<AttachmentId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub embed_index: Option<u64>,
}
impl CreateFavoriteMemeFromMessage {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
alt_text: None,
tags: None,
attachment_id: None,
embed_index: None,
}
}
pub fn alt_text(mut self, alt_text: impl Into<String>) -> Self {
self.alt_text = Some(alt_text.into());
self
}
pub fn tags(mut self, tags: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.tags = Some(tags.into_iter().map(Into::into).collect());
self
}
pub fn attachment_id(mut self, attachment_id: AttachmentId) -> Self {
self.attachment_id = Some(attachment_id);
self
}
pub fn embed_index(mut self, embed_index: u64) -> Self {
self.embed_index = Some(embed_index);
self
}
}
#[derive(Debug, Clone, Serialize)]
pub struct CreateFavoriteMemeFromUrl {
pub url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub alt_text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub klipy_slug: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tenor_slug_id: Option<String>,
}
impl CreateFavoriteMemeFromUrl {
pub fn new(url: impl Into<String>) -> Self {
Self {
url: url.into(),
name: None,
alt_text: None,
tags: None,
klipy_slug: None,
tenor_slug_id: None,
}
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn alt_text(mut self, alt_text: impl Into<String>) -> Self {
self.alt_text = Some(alt_text.into());
self
}
pub fn tags(mut self, tags: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.tags = Some(tags.into_iter().map(Into::into).collect());
self
}
pub fn klipy_slug(mut self, slug: impl Into<String>) -> Self {
self.klipy_slug = Some(slug.into());
self
}
pub fn tenor_slug_id(mut self, slug_id: impl Into<String>) -> Self {
self.tenor_slug_id = Some(slug_id.into());
self
}
}
#[derive(Debug, Default, Clone, Serialize)]
pub struct UpdateFavoriteMeme {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "NullableUpdate::is_unset")]
alt_text: NullableUpdate<String>,
#[serde(skip_serializing_if = "NullableUpdate::is_unset")]
tags: NullableUpdate<Vec<String>>,
}
impl UpdateFavoriteMeme {
pub fn new() -> Self {
Self::default()
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn alt_text(mut self, alt_text: impl Into<String>) -> Self {
self.alt_text = NullableUpdate::Value(alt_text.into());
self
}
pub fn clear_alt_text(mut self) -> Self {
self.alt_text = NullableUpdate::Clear;
self
}
pub fn tags(mut self, tags: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.tags = NullableUpdate::Value(tags.into_iter().map(Into::into).collect());
self
}
pub fn clear_tags(mut self) -> Self {
self.tags = NullableUpdate::Clear;
self
}
}
#[derive(Debug, Default, Clone)]
enum NullableUpdate<T> {
#[default]
Unset,
Value(T),
Clear,
}
impl<T> NullableUpdate<T> {
fn is_unset(&self) -> bool {
matches!(self, Self::Unset)
}
}
impl<T: Serialize> Serialize for NullableUpdate<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Self::Unset | Self::Clear => serializer.serialize_none(),
Self::Value(value) => value.serialize(serializer),
}
}
}
#[cfg(test)]
mod tests {
use super::UpdateFavoriteMeme;
use serde_json::json;
#[test]
fn update_favorite_meme_omits_unset_nullable_fields() {
let payload = serde_json::to_value(UpdateFavoriteMeme::new().name("renamed")).unwrap();
assert_eq!(payload, json!({ "name": "renamed" }));
}
#[test]
fn update_favorite_meme_serializes_nullable_field_values() {
let payload = serde_json::to_value(
UpdateFavoriteMeme::new()
.alt_text("description")
.tags(["tag-a", "tag-b"]),
)
.unwrap();
assert_eq!(
payload,
json!({
"alt_text": "description",
"tags": ["tag-a", "tag-b"],
})
);
}
#[test]
fn update_favorite_meme_serializes_nullable_field_clears() {
let payload =
serde_json::to_value(UpdateFavoriteMeme::new().clear_alt_text().clear_tags()).unwrap();
assert_eq!(
payload,
json!({
"alt_text": null,
"tags": null,
})
);
}
}