use std::sync::Arc;
use reqwest::Method;
use serde::{Deserialize, Serialize};
use crate::config::Config;
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum DomainStatus {
Pending,
Approved,
Blocked,
#[serde(untagged)]
Unknown(String),
}
impl std::fmt::Display for DomainStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Pending => write!(f, "pending"),
Self::Approved => write!(f, "approved"),
Self::Blocked => write!(f, "blocked"),
Self::Unknown(s) => write!(f, "{s}"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum DnsVerificationStatus {
Valid,
Invalid,
Missing,
Unverified,
NotApplicable,
#[serde(untagged)]
Unknown(String),
}
impl std::fmt::Display for DnsVerificationStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Valid => write!(f, "valid"),
Self::Invalid => write!(f, "invalid"),
Self::Missing => write!(f, "missing"),
Self::Unverified => write!(f, "unverified"),
Self::NotApplicable => write!(f, "not_applicable"),
Self::Unknown(s) => write!(f, "{s}"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum DmarcPolicy {
None,
Quarantine,
Reject,
#[serde(untagged)]
Unknown(String),
}
impl std::fmt::Display for DmarcPolicy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::None => write!(f, "none"),
Self::Quarantine => write!(f, "quarantine"),
Self::Reject => write!(f, "reject"),
Self::Unknown(s) => write!(f, "{s}"),
}
}
}
#[derive(Clone, Debug)]
pub struct DomainsSvc(pub(crate) Arc<Config>);
impl DomainsSvc {
#[maybe_async::maybe_async]
pub async fn list(&self) -> crate::Result<Vec<Domain>> {
let request = self.0.build(Method::GET, "/domains");
let response = self.0.send(request).await?;
let wrapper = response.json::<ListDomainsResponseWrapper>().await?;
Ok(wrapper.data.domains)
}
#[maybe_async::maybe_async]
pub async fn create(&self, domain: &str) -> crate::Result<CreateDomainResponse> {
let body = CreateDomainRequest {
domain: domain.to_owned(),
};
let request = self.0.build(Method::POST, "/domains").json(&body);
let response = self.0.send(request).await?;
let wrapper = response.json::<CreateDomainResponseWrapper>().await?;
Ok(wrapper.data)
}
#[maybe_async::maybe_async]
pub async fn get(&self, domain: &str) -> crate::Result<DomainDetail> {
let path = format!("/domains/{domain}");
let request = self.0.build(Method::GET, &path);
let response = self.0.send(request).await?;
let wrapper = response.json::<ShowDomainResponseWrapper>().await?;
Ok(wrapper.data)
}
#[maybe_async::maybe_async]
pub async fn delete(&self, domain: &str) -> crate::Result<()> {
let path = format!("/domains/{domain}");
let request = self.0.build(Method::DELETE, &path);
self.0.send(request).await?;
Ok(())
}
#[maybe_async::maybe_async]
pub async fn verify(&self, domain: &str) -> crate::Result<VerifyDomainResponse> {
let path = format!("/domains/{domain}/verify");
let request = self.0.build(Method::POST, &path);
let response = self.0.send(request).await?;
let wrapper = response.json::<VerifyDomainResponseWrapper>().await?;
Ok(wrapper.data)
}
}
#[derive(Debug, Serialize)]
struct CreateDomainRequest {
domain: String,
}
#[derive(Debug, Deserialize)]
struct ListDomainsResponseWrapper {
#[allow(dead_code)]
message: String,
data: ListDomainsData,
}
#[derive(Debug, Deserialize)]
struct ListDomainsData {
domains: Vec<Domain>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Domain {
pub domain: String,
pub status: DomainStatus,
pub status_label: String,
pub can_send: bool,
pub cname_status: Option<DnsVerificationStatus>,
pub dkim_status: Option<DnsVerificationStatus>,
pub created_at: String,
pub updated_at: String,
}
#[derive(Debug, Deserialize)]
struct CreateDomainResponseWrapper {
#[allow(dead_code)]
message: String,
data: CreateDomainResponse,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CreateDomainResponse {
pub domain: String,
pub status: DomainStatus,
pub status_label: String,
pub dkim: Option<DkimInfo>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct DkimInfo {
pub public: String,
pub selector: String,
pub headers: String,
#[serde(default)]
pub signing_domain: Option<String>,
}
#[derive(Debug, Deserialize)]
struct ShowDomainResponseWrapper {
#[allow(dead_code)]
message: String,
data: DomainDetail,
}
#[derive(Debug, Clone, Deserialize)]
pub struct DomainDetail {
pub domain: String,
pub status: DomainStatus,
pub status_label: String,
pub can_send: bool,
pub cname_status: Option<DnsVerificationStatus>,
pub dkim_status: Option<DnsVerificationStatus>,
#[serde(default)]
pub dmarc_status: Option<DnsVerificationStatus>,
#[serde(default)]
pub spf_status: Option<DnsVerificationStatus>,
#[serde(default)]
pub is_primary_domain: bool,
pub tracking_domain: Option<String>,
pub dns: Option<DnsRecords>,
#[serde(default)]
pub dns_provider: Option<DnsProvider>,
pub created_at: String,
pub updated_at: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct DnsRecords {
pub dkim: Option<DkimDnsRecord>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct DkimDnsRecord {
pub selector: String,
pub public: String,
#[serde(default)]
pub headers: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct DnsProvider {
pub provider: String,
pub provider_label: String,
pub nameservers: Vec<String>,
pub error: Option<String>,
}
#[derive(Debug, Deserialize)]
struct VerifyDomainResponseWrapper {
#[allow(dead_code)]
message: String,
data: VerifyDomainResponse,
}
#[derive(Debug, Clone, Deserialize)]
pub struct VerifyDomainResponse {
pub domain: String,
pub dkim_status: DnsVerificationStatus,
pub cname_status: DnsVerificationStatus,
pub dmarc_status: DnsVerificationStatus,
pub spf_status: DnsVerificationStatus,
pub is_primary_domain: bool,
#[serde(default)]
pub ownership_verified: Option<String>,
#[serde(default)]
pub dns: Option<DomainDnsVerification>,
#[serde(default)]
pub dmarc: Option<DmarcValidationResult>,
#[serde(default)]
pub spf: Option<SpfValidationResult>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct DomainDnsVerification {
#[serde(default)]
pub dkim_record: Option<String>,
#[serde(default)]
pub cname_record: Option<String>,
#[serde(default)]
pub dkim_error: Option<String>,
#[serde(default)]
pub cname_error: Option<String>,
#[serde(default)]
pub dmarc_record: Option<String>,
#[serde(default)]
pub dmarc_error: Option<String>,
#[serde(default)]
pub spf_record: Option<String>,
#[serde(default)]
pub spf_error: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct DmarcValidationResult {
pub is_valid: bool,
pub status: DnsVerificationStatus,
#[serde(default)]
pub found_at_domain: Option<String>,
#[serde(default)]
pub record: Option<String>,
#[serde(default)]
pub policy: Option<DmarcPolicy>,
#[serde(default)]
pub subdomain_policy: Option<DmarcPolicy>,
#[serde(default)]
pub error: Option<String>,
pub covered_by_parent_policy: bool,
}
#[derive(Debug, Clone, Deserialize)]
pub struct SpfValidationResult {
pub is_valid: bool,
pub status: DnsVerificationStatus,
#[serde(default)]
pub record: Option<String>,
#[serde(default)]
pub error: Option<String>,
pub includes_sparkpost: bool,
}