diff --git a/CHANGELOG.md b/CHANGELOG.md index 5355b3a..16e46c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,17 @@ and this project adheres to +## [0.8.1] - 2024-07-11 + +### Changed + +- `Email.text` is now optional + +### Fixed + +- The `cc`, `bcc` and `text` fields of the `Email` struct are nullable which broke deserialization. + It now works as expected. + ## [0.8.0] - 2024-07-05 ### Added @@ -123,6 +134,7 @@ Disabled `reqwest`'s default features and enabled `rustls-tls`. Initial release. +[0.8.1]: https://crates.io/crates/resend-rs/0.8.1 [0.8.0]: https://crates.io/crates/resend-rs/0.8.0 [0.7.0]: https://crates.io/crates/resend-rs/0.7.0 [0.6.0]: https://crates.io/crates/resend-rs/0.6.0 diff --git a/Cargo.toml b/Cargo.toml index f4f9568..ef916fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "resend-rs" -version = "0.8.0" +version = "0.8.1" edition = "2021" license = "MIT" @@ -34,4 +34,5 @@ governor = "0.6.3" rand = "0.8.5" [dev-dependencies] +serde_json = "1.0.120" tokio = { version = "1.37.0", features = ["macros", "test-util", "rt-multi-thread"] } diff --git a/src/emails.rs b/src/emails.rs index 97055f3..d56e7fd 100644 --- a/src/emails.rs +++ b/src/emails.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use reqwest::Method; +use serde::{Deserialize, Deserializer}; use crate::types::{CreateEmailBaseOptions, CreateEmailResponse, Email}; use crate::{Config, Result}; @@ -33,8 +34,6 @@ impl EmailsSvc { let request = self.0.build(Method::GET, &path); let response = self.0.send(request).await?; - // dbg!(response.text().await); - // todo!(); let content = response.json::().await?; Ok(content) @@ -48,6 +47,8 @@ pub mod types { use ecow::EcoString; use serde::{Deserialize, Serialize}; + use crate::emails::parse_nullable_vec; + /// Unique [`Email`] identifier. #[derive(Debug, Clone, Deserialize)] pub struct EmailId(EcoString); @@ -350,11 +351,13 @@ pub mod types { /// The HTML body of the email. pub html: Option, /// The plain text body of the email. - pub text: String, + pub text: Option, /// The email addresses of the blind carbon copy recipients. + #[serde(deserialize_with = "parse_nullable_vec")] pub bcc: Vec, /// The email addresses of the carbon copy recipients. + #[serde(deserialize_with = "parse_nullable_vec")] pub cc: Vec, /// The email addresses to which replies should be sent. pub reply_to: Option>, @@ -363,9 +366,20 @@ pub mod types { } } +/// Turns: +/// - `null` -> `[]` +/// - `["text"]` -> `["text"]` +fn parse_nullable_vec<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let opt = Option::deserialize(deserializer)?; + Ok(opt.unwrap_or_else(Vec::new)) +} + #[cfg(test)] mod test { - use crate::types::{CreateEmailBaseOptions, Tag}; + use crate::types::{CreateEmailBaseOptions, Email, Tag}; use crate::{tests::CLIENT, Resend, Result}; #[tokio::test] @@ -393,6 +407,57 @@ mod test { Ok(()) } + #[test] + fn deserialize_test() { + let email = r#"{ + "object": "email", + "id": "6757a66c-3a5b-49ee-98cc-fca7a5f423c0", + "to": [ + "email@gmail.com" + ], + "from": "email@gmail.com>", + "created_at": "2024-07-11 07:49:53.682607+00", + "subject": "Subject", + "bcc": null, + "cc": null, + "reply_to": null, + "last_event": "delivered", + "html": "
", + "text": null + }"#; + + let res = serde_json::from_str::(email); + assert!(res.is_ok()); + let res = res.unwrap(); + assert!(res.cc.is_empty()); + assert!(res.bcc.is_empty()); + assert!(res.text.is_none()); + + let email = r#"{ + "object": "email", + "id": "6757a66c-3a5b-49ee-98cc-fca7a5f423c0", + "to": [ + "email@gmail.com" + ], + "from": "email@gmail.com>", + "created_at": "2024-07-11 07:49:53.682607+00", + "subject": "Subject", + "bcc": ["hello", "world"], + "cc": ["!"], + "reply_to": null, + "last_event": "delivered", + "html": "
", + "text": "Not null" + }"#; + + let res = serde_json::from_str::(email); + assert!(res.is_ok()); + let res = res.unwrap(); + assert!(!res.cc.is_empty()); + assert!(!res.bcc.is_empty()); + assert!(res.text.is_some()); + } + #[test] #[cfg(feature = "blocking")] fn all_blocking() -> Result<()> {