use std::sync::Arc;
use reqwest::Method;
use crate::{
Config, Result,
list_opts::{ListOptions, ListResponse},
types::{
CreateTopicOptions, CreateTopicResponse, DeleteTopicResponse, Topic, UpdateTopicOptions,
UpdateTopicResponse,
},
};
#[derive(Clone, Debug)]
pub struct TopicsSvc(pub(crate) Arc<Config>);
impl TopicsSvc {
#[maybe_async::maybe_async]
#[allow(clippy::needless_pass_by_value)]
pub async fn create(&self, topic: CreateTopicOptions) -> Result<CreateTopicResponse> {
let request = self.0.build(Method::POST, "/topics");
let response = self.0.send(request.json(&topic)).await?;
let content = response.json::<CreateTopicResponse>().await?;
Ok(content)
}
#[maybe_async::maybe_async]
pub async fn get(&self, topic_id: &str) -> Result<Topic> {
let path = format!("/topics/{topic_id}");
let request = self.0.build(Method::GET, &path);
let response = self.0.send(request).await?;
let content = response.json::<Topic>().await?;
Ok(content)
}
#[maybe_async::maybe_async]
#[allow(clippy::needless_pass_by_value)]
pub async fn update(
&self,
topic_id: &str,
update: UpdateTopicOptions,
) -> Result<UpdateTopicResponse> {
let path = format!("/topics/{topic_id}");
let request = self.0.build(Method::PATCH, &path);
let response = self.0.send(request.json(&update)).await?;
let content = response.json::<UpdateTopicResponse>().await?;
Ok(content)
}
#[maybe_async::maybe_async]
pub async fn delete(&self, topic_id: &str) -> Result<DeleteTopicResponse> {
let path = format!("/topics/{topic_id}");
let request = self.0.build(Method::DELETE, &path);
let response = self.0.send(request).await?;
let content = response.json::<DeleteTopicResponse>().await?;
Ok(content)
}
#[maybe_async::maybe_async]
#[allow(clippy::needless_pass_by_value)]
pub async fn list<T>(&self, list_opts: ListOptions<T>) -> Result<ListResponse<Topic>> {
let request = self.0.build(Method::GET, "/topics").query(&list_opts);
let response = self.0.send(request).await?;
let content = response.json::<ListResponse<Topic>>().await?;
Ok(content)
}
}
#[allow(unreachable_pub)]
pub mod types {
use serde::{Deserialize, Serialize};
crate::define_id_type!(TopicId);
#[must_use]
#[derive(Debug, Clone, Serialize)]
pub struct CreateTopicOptions {
name: String,
default_subscription: SubscriptionType,
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
visibility: Option<TopicVisibility>,
}
#[must_use]
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum SubscriptionType {
OptIn,
OptOut,
}
impl CreateTopicOptions {
pub fn new(name: impl Into<String>, default_subscription: SubscriptionType) -> Self {
Self {
name: name.into(),
default_subscription,
description: None,
visibility: None,
}
}
#[inline]
pub fn with_description(mut self, description: String) -> Self {
self.description = Some(description);
self
}
#[inline]
pub const fn with_visibility(mut self, visibility: TopicVisibility) -> Self {
self.visibility = Some(visibility);
self
}
}
#[must_use]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateTopicResponse {
pub id: TopicId,
}
#[must_use]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Topic {
pub id: TopicId,
pub name: String,
pub description: Option<String>,
pub default_subscription: SubscriptionType,
pub visibility: TopicVisibility,
pub created_at: String,
}
#[must_use]
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum TopicVisibility {
Public,
Private,
}
#[must_use]
#[derive(Debug, Default, Clone, Serialize)]
pub struct UpdateTopicOptions {
name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
visibility: Option<TopicVisibility>,
}
impl UpdateTopicOptions {
pub const fn new() -> Self {
Self {
name: None,
description: None,
visibility: None,
}
}
#[inline]
pub fn with_name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
#[inline]
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
#[inline]
pub const fn with_visibility(mut self, visibility: TopicVisibility) -> Self {
self.visibility = Some(visibility);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpdateTopicResponse {
pub id: TopicId,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeleteTopicResponse {
pub id: TopicId,
pub deleted: bool,
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::needless_return)]
mod test {
use crate::list_opts::ListOptions;
use crate::test::{CLIENT, DebugResult};
use crate::types::{
CreateTopicOptions, SubscriptionType, Topic, TopicVisibility, UpdateTopicOptions,
};
#[tokio_shared_rt::test(shared = true)]
#[cfg(not(feature = "blocking"))]
#[ignore = "Flaky backend"]
async fn all() -> DebugResult<()> {
let resend = &*CLIENT;
let topic = CreateTopicOptions::new("Weekly Newsletter", SubscriptionType::OptIn)
.with_visibility(TopicVisibility::Public);
let topic = resend.topics.create(topic).await?;
std::thread::sleep(std::time::Duration::from_secs(1));
let topic = resend.topics.get(&topic.id).await?;
assert_eq!(topic.visibility, TopicVisibility::Public);
let update = UpdateTopicOptions::new()
.with_name("Weekly Newsletter")
.with_description("Weekly newsletter for our subscribers")
.with_visibility(TopicVisibility::Private);
let topic = resend.topics.update(&topic.id, update).await?;
std::thread::sleep(std::time::Duration::from_secs(4));
let topics = resend.topics.list(ListOptions::default()).await?;
assert!(topics.len() == 1, "{}", format!("Was {}", topics.len()));
assert_eq!(
topics.data.first().unwrap().visibility,
TopicVisibility::Private
);
let deleted = resend.topics.delete(&topic.id).await?;
assert!(deleted.deleted);
std::thread::sleep(std::time::Duration::from_secs(4));
let topics = resend.topics.list(ListOptions::default()).await?;
assert!(topics.is_empty());
Ok(())
}
#[test]
fn deserialize_test() {
let topic = r#"{
"id": "b6d24b8e-af0b-4c3c-be0c-359bbd97381e",
"name": "Weekly Newsletter",
"description": "Weekly newsletter for our subscribers",
"default_subscription": "opt_in",
"visibility": "public",
"created_at": "2023-04-08T00:11:13.110779+00:00"
}"#;
let res = serde_json::from_str::<Topic>(topic);
assert!(res.is_ok());
}
}