Skip to main content

zlayer_types/api/
notifiers.rs

1//! Notifier API DTOs.
2//!
3//! Wire types for the notifier CRUD and test-notification endpoints. The
4//! storage-resident types ([`NotifierKind`], [`NotifierConfig`],
5//! [`StoredNotifier`]) live in [`crate::storage`] and are referenced here.
6
7use serde::{Deserialize, Serialize};
8
9use crate::storage::{NotifierConfig, NotifierKind};
10
11/// Body for `POST /api/v1/notifiers`.
12#[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)]
13pub struct CreateNotifierRequest {
14    /// Notifier name.
15    pub name: String,
16    /// Notification channel type.
17    pub kind: NotifierKind,
18    /// Channel-specific configuration.
19    pub config: NotifierConfig,
20}
21
22/// Body for `PATCH /api/v1/notifiers/{id}`.
23#[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)]
24pub struct UpdateNotifierRequest {
25    /// Updated name.
26    #[serde(default)]
27    pub name: Option<String>,
28    /// Updated enabled flag.
29    #[serde(default)]
30    pub enabled: Option<bool>,
31    /// Updated configuration.
32    #[serde(default)]
33    pub config: Option<NotifierConfig>,
34}
35
36/// Response from `POST /api/v1/notifiers/{id}/test`.
37#[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)]
38pub struct TestNotifierResponse {
39    /// Whether the test notification was sent successfully.
40    pub success: bool,
41    /// Status message.
42    pub message: String,
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48
49    #[test]
50    fn test_create_request_deserialize_slack() {
51        let req: CreateNotifierRequest = serde_json::from_str(
52            r#"{"name":"slack-alerts","kind":"slack","config":{"type":"slack","webhook_url":"https://hooks.slack.com/test"}}"#,
53        )
54        .unwrap();
55        assert_eq!(req.name, "slack-alerts");
56        assert_eq!(req.kind, NotifierKind::Slack);
57        match &req.config {
58            NotifierConfig::Slack { webhook_url } => {
59                assert_eq!(webhook_url, "https://hooks.slack.com/test");
60            }
61            other => panic!("Expected Slack config, got {other:?}"),
62        }
63    }
64
65    #[test]
66    fn test_create_request_deserialize_discord() {
67        let req: CreateNotifierRequest = serde_json::from_str(
68            r#"{"name":"discord-alerts","kind":"discord","config":{"type":"discord","webhook_url":"https://discord.com/api/webhooks/test"}}"#,
69        )
70        .unwrap();
71        assert_eq!(req.kind, NotifierKind::Discord);
72    }
73
74    #[test]
75    fn test_create_request_deserialize_webhook() {
76        let req: CreateNotifierRequest = serde_json::from_str(
77            r#"{
78                "name": "generic-hook",
79                "kind": "webhook",
80                "config": {
81                    "type": "webhook",
82                    "url": "https://example.com/hook",
83                    "method": "PUT",
84                    "headers": {"Authorization": "Bearer token123"}
85                }
86            }"#,
87        )
88        .unwrap();
89        assert_eq!(req.kind, NotifierKind::Webhook);
90        match &req.config {
91            NotifierConfig::Webhook {
92                url,
93                method,
94                headers,
95            } => {
96                assert_eq!(url, "https://example.com/hook");
97                assert_eq!(method.as_deref(), Some("PUT"));
98                let h = headers.as_ref().unwrap();
99                assert_eq!(h.get("Authorization").unwrap(), "Bearer token123");
100            }
101            other => panic!("Expected Webhook config, got {other:?}"),
102        }
103    }
104
105    #[test]
106    fn test_create_request_deserialize_smtp() {
107        let req: CreateNotifierRequest = serde_json::from_str(
108            r#"{
109                "name": "email-alerts",
110                "kind": "smtp",
111                "config": {
112                    "type": "smtp",
113                    "host": "smtp.example.com",
114                    "port": 587,
115                    "username": "user",
116                    "password": "pass",
117                    "from": "noreply@example.com",
118                    "to": ["admin@example.com"]
119                }
120            }"#,
121        )
122        .unwrap();
123        assert_eq!(req.kind, NotifierKind::Smtp);
124    }
125
126    #[test]
127    fn test_update_request_partial() {
128        let req: UpdateNotifierRequest = serde_json::from_str(r#"{"enabled": false}"#).unwrap();
129        assert!(req.name.is_none());
130        assert_eq!(req.enabled, Some(false));
131        assert!(req.config.is_none());
132    }
133
134    #[test]
135    fn test_test_notifier_response_serialize() {
136        let resp = TestNotifierResponse {
137            success: true,
138            message: "ok".to_string(),
139        };
140        let json = serde_json::to_string(&resp).unwrap();
141        assert!(json.contains("\"success\":true"));
142        assert!(json.contains("\"message\":\"ok\""));
143    }
144}