use super::WebHookTrigger;
use base64::{engine::general_purpose, Engine};
use chrono::{DateTime, Local};
use mycelium_base::utils::errors::{dto_err, MappedErrors};
use serde::{Deserialize, Serialize};
use std::{fmt::Display, str::FromStr};
use utoipa::ToSchema;
use uuid::Uuid;
#[derive(Clone, Debug, Deserialize, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub enum WebHookExecutionStatus {
Pending,
Success,
Failed,
Skipped,
Unknown,
}
impl Display for WebHookExecutionStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Pending => write!(f, "pending"),
Self::Success => write!(f, "success"),
Self::Failed => write!(f, "failed"),
Self::Skipped => write!(f, "skipped"),
Self::Unknown => write!(f, "unknown"),
}
}
}
impl FromStr for WebHookExecutionStatus {
type Err = MappedErrors;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"pending" => Ok(Self::Pending),
"success" => Ok(Self::Success),
"failed" => Ok(Self::Failed),
"skipped" => Ok(Self::Skipped),
"unknown" => Ok(Self::Unknown),
_ => dto_err("Invalid webhook execution status").as_error(),
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct HookResponse {
pub url: String,
pub status: u16,
pub body: Option<String>,
pub datetime: DateTime<Local>,
}
#[derive(Clone, Debug, Deserialize, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub enum PayloadId {
Uuid(Uuid),
String(String),
Number(u64),
}
impl Display for PayloadId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Uuid(uuid) => write!(f, "{}", uuid),
Self::String(string) => write!(f, "{}", string),
Self::Number(number) => write!(f, "{}", number),
}
}
}
impl FromStr for PayloadId {
type Err = MappedErrors;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(uuid) = s.parse::<Uuid>() {
Ok(Self::Uuid(uuid))
} else if let Ok(number) = s.parse::<u64>() {
Ok(Self::Number(number))
} else {
Ok(Self::String(s.to_string()))
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct WebHookPayloadArtifact {
pub id: Option<Uuid>,
pub payload: String,
pub payload_id: PayloadId,
pub trigger: WebHookTrigger,
#[serde(skip_serializing_if = "Option::is_none")]
pub propagations: Option<Vec<HookResponse>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub encrypted: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub attempts: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub attempted: Option<DateTime<Local>>,
pub created: Option<DateTime<Local>>,
pub status: Option<WebHookExecutionStatus>,
}
impl WebHookPayloadArtifact {
pub fn new(
id: Option<Uuid>,
payload: String,
payload_id: PayloadId,
trigger: WebHookTrigger,
) -> Self {
Self {
id,
payload,
payload_id,
trigger,
propagations: None,
encrypted: None,
attempts: None,
attempted: None,
created: None,
status: Some(WebHookExecutionStatus::Pending),
}
}
pub fn encode_payload(&mut self) -> Result<Self, MappedErrors> {
let serialized_payload =
serde_json::to_string(&self.payload).map_err(|e| {
dto_err(format!("Failed to serialize payload: {}", e))
})?;
let encoded_payload =
general_purpose::STANDARD.encode(serialized_payload.as_bytes());
Ok(Self {
payload: encoded_payload,
..self.clone()
})
}
pub fn decode_payload(
&self,
) -> Result<WebHookPayloadArtifact, MappedErrors> {
let decoded_payload =
match general_purpose::STANDARD.decode(&self.payload) {
Err(_) => return dto_err("Failed to decode base64").as_error(),
Ok(decoded) => String::from_utf8(decoded)
.map_err(|_| dto_err("Failed to decode payload"))?,
};
let payload = serde_json::from_str(&decoded_payload).map_err(|e| {
dto_err(format!("Failed to deserialize payload: {}", e))
})?;
Ok(Self {
payload,
..self.clone()
})
}
}