use std::sync::Arc;
use reqwest::Method;
use serde::{Deserialize, Serialize};
use crate::config::Config;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum WebhookAuthType {
None,
Basic,
Oauth2,
#[serde(untagged)]
Unknown(String),
}
impl std::fmt::Display for WebhookAuthType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::None => write!(f, "none"),
Self::Basic => write!(f, "basic"),
Self::Oauth2 => write!(f, "oauth2"),
Self::Unknown(s) => write!(f, "{s}"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum WebhookStatus {
Success,
Failure,
#[serde(untagged)]
Unknown(String),
}
impl std::fmt::Display for WebhookStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Success => write!(f, "success"),
Self::Failure => write!(f, "failure"),
Self::Unknown(s) => write!(f, "{s}"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum WebhookEventsMode {
All,
Selected,
}
impl std::fmt::Display for WebhookEventsMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::All => write!(f, "all"),
Self::Selected => write!(f, "selected"),
}
}
}
pub mod event_types {
pub const INJECTION: &str = "message.injection";
pub const DELIVERY: &str = "message.delivery";
pub const BOUNCE: &str = "message.bounce";
pub const DELAY: &str = "message.delay";
pub const OUT_OF_BAND: &str = "message.out_of_band";
pub const SPAM_COMPLAINT: &str = "message.spam_complaint";
pub const POLICY_REJECTION: &str = "message.policy_rejection";
pub const CLICK: &str = "engagement.click";
pub const OPEN: &str = "engagement.open";
pub const INITIAL_OPEN: &str = "engagement.initial_open";
pub const AMP_CLICK: &str = "engagement.amp_click";
pub const AMP_OPEN: &str = "engagement.amp_open";
pub const AMP_INITIAL_OPEN: &str = "engagement.amp_initial_open";
pub const GENERATION_FAILURE: &str = "generation.generation_failure";
pub const GENERATION_REJECTION: &str = "generation.generation_rejection";
pub const LIST_UNSUBSCRIBE: &str = "unsubscribe.list_unsubscribe";
pub const LINK_UNSUBSCRIBE: &str = "unsubscribe.link_unsubscribe";
pub const RELAY_INJECTION: &str = "relay.relay_injection";
pub const RELAY_REJECTION: &str = "relay.relay_rejection";
pub const RELAY_DELIVERY: &str = "relay.relay_delivery";
pub const RELAY_TEMPFAIL: &str = "relay.relay_tempfail";
pub const RELAY_PERMFAIL: &str = "relay.relay_permfail";
}
#[derive(Clone, Debug)]
pub struct WebhooksSvc(pub(crate) Arc<Config>);
impl WebhooksSvc {
#[maybe_async::maybe_async]
pub async fn list(&self) -> crate::Result<Vec<Webhook>> {
let request = self.0.build(Method::GET, "/webhooks");
let response = self.0.send(request).await?;
let wrapper = response.json::<ListWebhooksResponseWrapper>().await?;
Ok(wrapper.data.webhooks)
}
#[maybe_async::maybe_async]
pub async fn get(&self, webhook_id: &str) -> crate::Result<Webhook> {
let path = format!("/webhooks/{webhook_id}");
let request = self.0.build(Method::GET, &path);
let response = self.0.send(request).await?;
let wrapper = response.json::<ShowWebhookResponseWrapper>().await?;
Ok(wrapper.data)
}
#[maybe_async::maybe_async]
pub async fn create(&self, options: CreateWebhookOptions) -> crate::Result<Webhook> {
let request = self.0.build(Method::POST, "/webhooks").json(&options);
let response = self.0.send(request).await?;
let wrapper = response.json::<ShowWebhookResponseWrapper>().await?;
Ok(wrapper.data)
}
#[maybe_async::maybe_async]
pub async fn update(
&self,
webhook_id: &str,
options: UpdateWebhookOptions,
) -> crate::Result<Webhook> {
let path = format!("/webhooks/{webhook_id}");
let request = self.0.build(Method::PUT, &path).json(&options);
let response = self.0.send(request).await?;
let wrapper = response.json::<ShowWebhookResponseWrapper>().await?;
Ok(wrapper.data)
}
#[maybe_async::maybe_async]
pub async fn delete(&self, webhook_id: &str) -> crate::Result<()> {
let path = format!("/webhooks/{webhook_id}");
let request = self.0.build(Method::DELETE, &path);
self.0.send(request).await?;
Ok(())
}
}
#[must_use]
#[derive(Debug, Clone, Serialize)]
pub struct CreateWebhookOptions {
name: String,
url: String,
auth_type: WebhookAuthType,
events_mode: WebhookEventsMode,
#[serde(skip_serializing_if = "Option::is_none")]
events: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
auth_username: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
auth_password: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
oauth_client_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
oauth_client_secret: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
oauth_token_url: Option<String>,
}
impl CreateWebhookOptions {
pub fn new(
name: impl Into<String>,
url: impl Into<String>,
auth_type: WebhookAuthType,
events_mode: WebhookEventsMode,
) -> Self {
Self {
name: name.into(),
url: url.into(),
auth_type,
events_mode,
events: None,
auth_username: None,
auth_password: None,
oauth_client_id: None,
oauth_client_secret: None,
oauth_token_url: None,
}
}
#[inline]
pub fn with_events(mut self, events: Vec<String>) -> Self {
self.events = Some(events);
self
}
#[inline]
pub fn with_basic_auth(
mut self,
username: impl Into<String>,
password: impl Into<String>,
) -> Self {
self.auth_username = Some(username.into());
self.auth_password = Some(password.into());
self
}
#[inline]
pub fn with_oauth2(
mut self,
client_id: impl Into<String>,
client_secret: impl Into<String>,
token_url: impl Into<String>,
) -> Self {
self.oauth_client_id = Some(client_id.into());
self.oauth_client_secret = Some(client_secret.into());
self.oauth_token_url = Some(token_url.into());
self
}
}
#[must_use]
#[derive(Debug, Default, Clone, Serialize)]
pub struct UpdateWebhookOptions {
#[serde(skip_serializing_if = "Option::is_none")]
name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
target: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
auth_type: Option<WebhookAuthType>,
#[serde(skip_serializing_if = "Option::is_none")]
auth_username: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
auth_password: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
oauth_token_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
oauth_client_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
oauth_client_secret: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
events: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
active: Option<bool>,
}
impl UpdateWebhookOptions {
pub fn new() -> Self {
Self::default()
}
#[inline]
pub fn with_name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
#[inline]
pub fn with_target(mut self, target: impl Into<String>) -> Self {
self.target = Some(target.into());
self
}
#[inline]
pub fn with_auth_type(mut self, auth_type: WebhookAuthType) -> Self {
self.auth_type = Some(auth_type);
self
}
#[inline]
pub fn with_basic_auth(
mut self,
username: impl Into<String>,
password: impl Into<String>,
) -> Self {
self.auth_username = Some(username.into());
self.auth_password = Some(password.into());
self
}
#[inline]
pub fn with_oauth2(
mut self,
client_id: impl Into<String>,
client_secret: impl Into<String>,
token_url: impl Into<String>,
) -> Self {
self.oauth_client_id = Some(client_id.into());
self.oauth_client_secret = Some(client_secret.into());
self.oauth_token_url = Some(token_url.into());
self
}
#[inline]
pub fn with_events(mut self, events: Vec<String>) -> Self {
self.events = Some(events);
self
}
#[inline]
pub fn with_active(mut self, active: bool) -> Self {
self.active = Some(active);
self
}
}
#[derive(Debug, Deserialize)]
struct ListWebhooksResponseWrapper {
#[allow(dead_code)]
message: String,
data: ListWebhooksData,
}
#[derive(Debug, Deserialize)]
struct ListWebhooksData {
webhooks: Vec<Webhook>,
}
#[derive(Debug, Deserialize)]
struct ShowWebhookResponseWrapper {
#[allow(dead_code)]
message: String,
data: Webhook,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Webhook {
pub id: String,
pub name: String,
pub url: String,
pub enabled: bool,
pub event_types: Option<Vec<String>>,
pub auth_type: WebhookAuthType,
pub has_auth_credentials: bool,
pub last_successful_at: Option<String>,
pub last_failure_at: Option<String>,
pub last_status: Option<WebhookStatus>,
}