use crate::error::{DrivenError, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ZapierConfig {
#[serde(default = "default_true")]
pub enabled: bool,
#[serde(default)]
pub triggers: HashMap<String, ZapierTrigger>,
}
fn default_true() -> bool {
true
}
impl Default for ZapierConfig {
fn default() -> Self {
Self {
enabled: true,
triggers: HashMap::new(),
}
}
}
impl ZapierConfig {
pub fn from_file(path: impl AsRef<std::path::Path>) -> Result<Self> {
let content = std::fs::read_to_string(path.as_ref())
.map_err(|e| DrivenError::Io(e))?;
Self::parse_sr(&content)
}
fn parse_sr(_content: &str) -> Result<Self> {
Ok(Self::default())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ZapierTrigger {
pub name: String,
pub webhook_url: String,
pub description: Option<String>,
#[serde(default = "default_true")]
pub enabled: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ZapierResponse {
pub status: String,
pub id: Option<String>,
pub attempt: Option<String>,
}
pub struct ZapierClient {
config: ZapierConfig,
}
impl ZapierClient {
pub fn new(config: &ZapierConfig) -> Result<Self> {
Ok(Self {
config: config.clone(),
})
}
pub fn is_configured(&self) -> bool {
self.config.enabled && !self.config.triggers.is_empty()
}
pub async fn trigger(&self, name: &str, data: serde_json::Value) -> Result<ZapierResponse> {
let trigger = self.config.triggers.get(name)
.ok_or_else(|| DrivenError::NotFound(format!("Trigger '{}' not found", name)))?;
if !trigger.enabled {
return Err(DrivenError::Config("Trigger is disabled".into()));
}
self.send_webhook(&trigger.webhook_url, data).await
}
pub async fn trigger_webhook(&self, url: &str, data: serde_json::Value) -> Result<ZapierResponse> {
self.send_webhook(url, data).await
}
async fn send_webhook(&self, url: &str, data: serde_json::Value) -> Result<ZapierResponse> {
let client = reqwest::Client::new();
let response = client
.post(url)
.header("Content-Type", "application/json")
.json(&data)
.send()
.await
.map_err(|e| DrivenError::Network(e.to_string()))?;
if !response.status().is_success() {
let status = response.status();
let error_text = response.text().await.unwrap_or_default();
return Err(DrivenError::Api(format!(
"Zapier webhook error ({}): {}",
status, error_text
)));
}
Ok(ZapierResponse {
status: "success".to_string(),
id: None,
attempt: None,
})
}
pub fn list_triggers(&self) -> Vec<&ZapierTrigger> {
self.config.triggers.values().collect()
}
pub fn add_trigger(&mut self, trigger: ZapierTrigger) {
self.config.triggers.insert(trigger.name.clone(), trigger);
}
pub fn remove_trigger(&mut self, name: &str) -> Option<ZapierTrigger> {
self.config.triggers.remove(name)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = ZapierConfig::default();
assert!(config.enabled);
assert!(config.triggers.is_empty());
}
#[test]
fn test_client_creation() {
let config = ZapierConfig::default();
let client = ZapierClient::new(&config);
assert!(client.is_ok());
}
}