Skip to main content

worldinterface_http_trigger/
trigger.rs

1//! Trigger input data written to ContextStore when a webhook fires.
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6/// Structured trigger input written to ContextStore when a webhook fires.
7///
8/// Flow nodes access this via `{{trigger.body}}`, `{{trigger.headers}}`, etc.
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct TriggerInput {
11    /// The HTTP request body (parsed as JSON if valid, or wrapped as a JSON string).
12    pub body: Value,
13
14    /// HTTP request headers as a flat JSON object (header-name -> value).
15    pub headers: Value,
16
17    /// The HTTP method (always "POST" for webhooks, included for completeness).
18    pub method: String,
19
20    /// The webhook path that was matched.
21    pub path: String,
22
23    /// Source IP address of the request (from `x-forwarded-for` or connection peer).
24    #[serde(default, skip_serializing_if = "Option::is_none")]
25    pub source_addr: Option<String>,
26
27    /// Unix timestamp (seconds) of when the webhook was received.
28    pub received_at: u64,
29}
30
31#[cfg(test)]
32mod tests {
33    use serde_json::json;
34
35    use super::*;
36
37    fn sample_trigger_input() -> TriggerInput {
38        TriggerInput {
39            body: json!({"event": "push", "ref": "refs/heads/main"}),
40            headers: json!({"content-type": "application/json", "x-github-event": "push"}),
41            method: "POST".to_string(),
42            path: "github/push".to_string(),
43            source_addr: Some("192.168.1.1".to_string()),
44            received_at: 1741200000,
45        }
46    }
47
48    #[test]
49    fn json_body_preserved_as_json() {
50        let input = sample_trigger_input();
51        assert!(input.body.is_object());
52        assert_eq!(input.body["event"], "push");
53    }
54
55    #[test]
56    fn non_json_body_becomes_string() {
57        let input = TriggerInput {
58            body: Value::String("plain text body".to_string()),
59            headers: json!({}),
60            method: "POST".to_string(),
61            path: "test".to_string(),
62            source_addr: None,
63            received_at: 0,
64        };
65        assert!(input.body.is_string());
66        assert_eq!(input.body.as_str().unwrap(), "plain text body");
67    }
68
69    #[test]
70    fn trigger_input_serializes() {
71        let input = sample_trigger_input();
72        let json = serde_json::to_string(&input).unwrap();
73        let back: TriggerInput = serde_json::from_str(&json).unwrap();
74        assert_eq!(input.body, back.body);
75        assert_eq!(input.headers, back.headers);
76        assert_eq!(input.method, back.method);
77        assert_eq!(input.path, back.path);
78        assert_eq!(input.source_addr, back.source_addr);
79        assert_eq!(input.received_at, back.received_at);
80    }
81
82    #[test]
83    fn headers_as_json_object() {
84        let input = sample_trigger_input();
85        let headers = input.headers.as_object().unwrap();
86        assert_eq!(headers.len(), 2);
87        assert_eq!(headers["content-type"], "application/json");
88        assert_eq!(headers["x-github-event"], "push");
89    }
90}