use serde::{Deserialize, Serialize};
use std::time::Duration;
use crate::contracts::WebhookConfig;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WebhookEventType {
TaskCreated,
TaskStarted,
TaskCompleted,
TaskFailed,
TaskStatusChanged,
LoopStarted,
LoopStopped,
PhaseStarted,
PhaseCompleted,
QueueUnblocked,
}
impl WebhookEventType {
pub fn as_str(&self) -> &'static str {
match self {
WebhookEventType::TaskCreated => "task_created",
WebhookEventType::TaskStarted => "task_started",
WebhookEventType::TaskCompleted => "task_completed",
WebhookEventType::TaskFailed => "task_failed",
WebhookEventType::TaskStatusChanged => "task_status_changed",
WebhookEventType::LoopStarted => "loop_started",
WebhookEventType::LoopStopped => "loop_stopped",
WebhookEventType::PhaseStarted => "phase_started",
WebhookEventType::PhaseCompleted => "phase_completed",
WebhookEventType::QueueUnblocked => "queue_unblocked",
}
}
}
impl std::str::FromStr for WebhookEventType {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"task_created" => Self::TaskCreated,
"task_started" => Self::TaskStarted,
"task_completed" => Self::TaskCompleted,
"task_failed" => Self::TaskFailed,
"task_status_changed" => Self::TaskStatusChanged,
"loop_started" => Self::LoopStarted,
"loop_stopped" => Self::LoopStopped,
"phase_started" => Self::PhaseStarted,
"phase_completed" => Self::PhaseCompleted,
"queue_unblocked" => Self::QueueUnblocked,
other => anyhow::bail!(
"Unknown event type: {}. Supported: task_created, task_started, task_completed, task_failed, task_status_changed, loop_started, loop_stopped, phase_started, phase_completed, queue_unblocked",
other
),
})
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct WebhookContext {
#[serde(skip_serializing_if = "Option::is_none")]
pub runner: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub phase: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub phase_count: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub duration_ms: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub repo_root: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub branch: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub commit: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ci_gate: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebhookPayload {
pub event: String,
pub timestamp: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub task_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub task_title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub previous_status: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub current_status: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub note: Option<String>,
#[serde(flatten)]
pub context: WebhookContext,
}
#[derive(Debug, Clone)]
pub struct ResolvedWebhookConfig {
pub enabled: bool,
pub url: Option<String>,
pub secret: Option<String>,
pub timeout: Duration,
pub retry_count: u32,
pub retry_backoff: Duration,
pub allow_insecure_http: bool,
pub allow_private_targets: bool,
}
impl ResolvedWebhookConfig {
pub fn from_config(config: &WebhookConfig) -> Self {
Self {
enabled: config.enabled.unwrap_or(false),
url: config.url.clone(),
secret: config.secret.clone(),
timeout: Duration::from_secs(config.timeout_secs.unwrap_or(30) as u64),
retry_count: config.retry_count.unwrap_or(3),
retry_backoff: Duration::from_millis(config.retry_backoff_ms.unwrap_or(1000) as u64),
allow_insecure_http: config.allow_insecure_http.unwrap_or(false),
allow_private_targets: config.allow_private_targets.unwrap_or(false),
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct WebhookMessage {
pub(crate) payload: WebhookPayload,
pub(crate) config: ResolvedWebhookConfig,
}
pub(crate) fn resolve_webhook_config(config: &WebhookConfig) -> ResolvedWebhookConfig {
ResolvedWebhookConfig::from_config(config)
}