openzeppelin_relayer/models/notification/
repository.rs

1//! Repository layer models and data persistence for notifications.
2//!
3//! This module provides the data layer representation of notifications, including:
4//!
5//! - **Repository Models**: Data structures optimized for storage and retrieval
6//! - **Data Conversions**: Mapping between domain objects and repository representations
7//! - **Persistence Logic**: Storage-specific validation and constraints
8//!
9//! Acts as the bridge between the domain layer and actual data storage implementations
10//! (in-memory, Redis, etc.), ensuring consistent data representation across repositories.
11
12use crate::models::{
13    notification::Notification, NotificationType, NotificationValidationError, SecretString,
14};
15use crate::utils::{deserialize_option_secret_string, serialize_option_secret_string};
16use serde::{Deserialize, Serialize};
17use utoipa::ToSchema;
18
19#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ToSchema)]
20pub struct NotificationRepoModel {
21    pub id: String,
22    pub notification_type: NotificationType,
23    pub url: String,
24    #[serde(
25        serialize_with = "serialize_option_secret_string",
26        deserialize_with = "deserialize_option_secret_string"
27    )]
28    pub signing_key: Option<SecretString>,
29}
30
31impl From<Notification> for NotificationRepoModel {
32    fn from(notification: Notification) -> Self {
33        Self {
34            id: notification.id,
35            notification_type: notification.notification_type,
36            url: notification.url,
37            signing_key: notification.signing_key,
38        }
39    }
40}
41
42impl From<NotificationRepoModel> for Notification {
43    fn from(repo_model: NotificationRepoModel) -> Self {
44        Self {
45            id: repo_model.id,
46            notification_type: repo_model.notification_type,
47            url: repo_model.url,
48            signing_key: repo_model.signing_key,
49        }
50    }
51}
52
53impl NotificationRepoModel {
54    /// Validates the repository model using core validation logic
55    pub fn validate(&self) -> Result<(), NotificationValidationError> {
56        let core_notification = Notification::from(self.clone());
57        core_notification.validate()
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64    use crate::models::SecretString;
65
66    #[test]
67    fn test_from_core_notification() {
68        let core = Notification::new(
69            "test-id".to_string(),
70            NotificationType::Webhook,
71            "https://example.com/webhook".to_string(),
72            Some(SecretString::new("test-key")),
73        );
74
75        let repo_model = NotificationRepoModel::from(core);
76        assert_eq!(repo_model.id, "test-id");
77        assert_eq!(repo_model.notification_type, NotificationType::Webhook);
78        assert_eq!(repo_model.url, "https://example.com/webhook");
79        assert!(repo_model.signing_key.is_some());
80    }
81
82    #[test]
83    fn test_to_core_notification() {
84        let repo_model = NotificationRepoModel {
85            id: "test-id".to_string(),
86            notification_type: NotificationType::Webhook,
87            url: "https://example.com/webhook".to_string(),
88            signing_key: Some(SecretString::new("test-key")),
89        };
90
91        let core = Notification::from(repo_model);
92        assert_eq!(core.id, "test-id");
93        assert_eq!(core.notification_type, NotificationType::Webhook);
94        assert_eq!(core.url, "https://example.com/webhook");
95        assert!(core.signing_key.is_some());
96    }
97
98    #[test]
99    fn test_validation() {
100        let repo_model = NotificationRepoModel {
101            id: "test-id".to_string(),
102            notification_type: NotificationType::Webhook,
103            url: "https://example.com/webhook".to_string(),
104            signing_key: Some(SecretString::new(&"a".repeat(32))),
105        };
106
107        assert!(repo_model.validate().is_ok());
108    }
109}