forge_core/webhook/
traits.rs1use std::future::Future;
2use std::pin::Pin;
3use std::time::Duration;
4
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8use crate::error::Result;
9
10use super::context::WebhookContext;
11use super::signature::{IdempotencyConfig, SignatureConfig};
12
13pub trait ForgeWebhook: Send + Sync + 'static {
18 fn info() -> WebhookInfo;
20
21 fn execute(
27 ctx: &WebhookContext,
28 payload: Value,
29 ) -> Pin<Box<dyn Future<Output = Result<WebhookResult>> + Send + '_>>;
30}
31
32#[derive(Debug, Clone)]
34pub struct WebhookInfo {
35 pub name: &'static str,
37 pub path: &'static str,
39 pub signature: Option<SignatureConfig>,
41 pub idempotency: Option<IdempotencyConfig>,
43 pub timeout: Duration,
45}
46
47impl Default for WebhookInfo {
48 fn default() -> Self {
49 Self {
50 name: "",
51 path: "",
52 signature: None,
53 idempotency: None,
54 timeout: Duration::from_secs(30),
55 }
56 }
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
61#[serde(tag = "status")]
62pub enum WebhookResult {
63 #[serde(rename = "ok")]
65 Ok,
66 #[serde(rename = "accepted")]
68 Accepted,
69 #[serde(rename = "custom")]
71 Custom {
72 status_code: u16,
74 body: Value,
76 },
77}
78
79impl WebhookResult {
80 pub fn status_code(&self) -> u16 {
82 match self {
83 Self::Ok => 200,
84 Self::Accepted => 202,
85 Self::Custom { status_code, .. } => *status_code,
86 }
87 }
88
89 pub fn body(&self) -> Value {
91 match self {
92 Self::Ok => serde_json::json!({"status": "ok"}),
93 Self::Accepted => serde_json::json!({"status": "accepted"}),
94 Self::Custom { body, .. } => body.clone(),
95 }
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn test_default_webhook_info() {
105 let info = WebhookInfo::default();
106 assert!(info.signature.is_none());
107 assert!(info.idempotency.is_none());
108 assert_eq!(info.timeout, Duration::from_secs(30));
109 }
110
111 #[test]
112 fn test_webhook_result_status_codes() {
113 assert_eq!(WebhookResult::Ok.status_code(), 200);
114 assert_eq!(WebhookResult::Accepted.status_code(), 202);
115 assert_eq!(
116 WebhookResult::Custom {
117 status_code: 400,
118 body: serde_json::json!({"error": "bad request"})
119 }
120 .status_code(),
121 400
122 );
123 }
124
125 #[test]
126 fn test_webhook_result_body() {
127 assert_eq!(
128 WebhookResult::Ok.body(),
129 serde_json::json!({"status": "ok"})
130 );
131 assert_eq!(
132 WebhookResult::Accepted.body(),
133 serde_json::json!({"status": "accepted"})
134 );
135 }
136}