Skip to main content

openleadr_wire/
subscription.rs

1use std::{
2    fmt::{Display, Formatter},
3    str::FromStr,
4};
5
6use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8use serde_with::{serde_as, skip_serializing_none};
9use validator::Validate;
10
11use crate::{
12    program::ProgramId, resource::Resource, Event, Identifier, IdentifierError, ObjectType,
13    Program, Report, Ven,
14};
15
16/// Server provided representation of subscription
17#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Validate)]
18#[serde(rename_all = "camelCase")]
19pub struct Subscription {
20    /// URL safe VTN assigned object ID.
21    pub id: SubscriptionId,
22    /// datetime in ISO 8601 format
23    #[serde(with = "crate::serde_rfc3339")]
24    pub created_date_time: DateTime<Utc>,
25    /// datetime in ISO 8601 format
26    #[serde(with = "crate::serde_rfc3339")]
27    pub modification_date_time: DateTime<Utc>,
28    #[serde(flatten)]
29    #[validate(nested)]
30    pub content: SubscriptionRequest,
31}
32
33/// An object created by a client to receive notification of operations on objects.
34/// Clients may subscribe to be notified when a type of object is created,
35/// updated, or deleted.
36#[skip_serializing_none]
37#[serde_as]
38#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Validate)]
39#[serde(rename_all = "camelCase")]
40pub struct SubscriptionRequest {
41    /// User generated identifier, may be VEN identifier provisioned out-of-band.
42    pub client_name: String,
43
44    /// ID attribute of the program object this subscription is associated with.
45    #[serde(rename = "programID")]
46    pub program_id: Option<ProgramId>,
47
48    /// list of objects and operations to subscribe to.
49    pub object_operations: Vec<SubscriptionObjectOperation>,
50    // /// A list of target objects. Used by server to filter notifications.
51    // #[serde(default)]
52    // #[serde_as(deserialize_as = "DefaultOnNull")]
53    // pub targets: Vec<Target>,
54}
55
56#[skip_serializing_none]
57#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Validate)]
58#[serde(rename_all = "camelCase")]
59pub struct SubscriptionObjectOperation {
60    /// list of objects to subscribe to.
61    pub objects: Vec<ObjectType>,
62
63    /// list of operations to subscribe to.
64    pub operations: Vec<Operation>,
65
66    /// The transport mechanism used to deliver the notification
67    #[serde(default)]
68    pub mechanism: NotificationMechanism,
69
70    /// User provided webhook URL. Required if `mechanism` is "WEBHOOK"
71    pub callback_url: Option<String>,
72
73    /// User provided token.
74    /// To avoid custom integrations, callback endpoints
75    /// should accept the provided bearer token to authenticate VTN requests.
76    pub bearer_token: Option<String>,
77}
78
79#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
80#[serde(rename_all = "UPPERCASE")]
81pub enum Operation {
82    Create,
83    Update,
84    Delete,
85}
86
87#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
88#[serde(rename_all = "UPPERCASE")]
89pub enum NotificationMechanism {
90    #[default]
91    Webhook,
92    Websocket,
93}
94
95/// URL safe VTN assigned object ID
96#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Hash, Eq)]
97pub struct SubscriptionId(pub(crate) Identifier);
98
99impl Display for SubscriptionId {
100    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
101        write!(f, "{}", self.0)
102    }
103}
104
105impl SubscriptionId {
106    pub fn as_str(&self) -> &str {
107        self.0.as_str()
108    }
109}
110
111impl FromStr for SubscriptionId {
112    type Err = IdentifierError;
113
114    fn from_str(s: &str) -> Result<Self, Self::Err> {
115        Ok(Self(s.parse()?))
116    }
117}
118
119///  VTN generated object included in request to subscription callbackUrl.
120#[skip_serializing_none]
121#[serde_as]
122#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Validate)]
123#[serde(rename_all = "camelCase")]
124pub struct Notification {
125    /// A unique ID of the operation that triggered this notification.
126    /// Used to acknowledge receiving a notification over websockets and to allow a VEN to deduplicate notifications.
127    /// A duplication could happend due to multiple subscriptions of (partly) overlying operations, possibly over different
128    /// notification mechanisms, or if a webhook call returns an error code and gets therefore retried, for example.
129    ///
130    /// Note that this an ID of the operation (create, update, ...) that triggerd this notification. This means,
131    /// multiple subscribers can get the same ID, possibly over different notification channels (webhook, websocket, MQTT).
132    ///
133    /// The exact structure of this ID is up to the implementation, but using a counter or UUID is RECOMMENDED.
134    pub id: Identifier,
135
136    /// the operation on on object that triggered the notification.
137    pub operation: Operation,
138
139    /// the object that is the subject of the notification.
140    #[serde(flatten)]
141    pub object: AnyObject,
142    // /// A list of targets.
143    // #[serde(default)]
144    // #[serde_as(deserialize_as = "DefaultOnNull")]
145    // pub targets: Vec<Target>,
146}
147
148#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
149#[serde(tag = "objectType", content = "object", rename_all = "UPPERCASE")]
150pub enum AnyObject {
151    Program(Program),
152    Report(Report),
153    Event(Event),
154    Subscription(Subscription),
155    Ven(Ven),
156    Resource(Resource),
157}
158
159impl AnyObject {
160    pub fn id(&self) -> Identifier {
161        match self {
162            AnyObject::Program(program) => program.id.0.clone(),
163            AnyObject::Report(report) => report.id.0.clone(),
164            AnyObject::Event(event) => event.id.0.clone(),
165            AnyObject::Subscription(subscription) => subscription.id.0.clone(),
166            AnyObject::Ven(ven) => ven.id.0.clone(),
167            AnyObject::Resource(resource) => resource.id.0.clone(),
168        }
169    }
170}
171
172/// Provides details of each notifier binding supported
173#[skip_serializing_none]
174#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Validate)]
175#[serde(rename_all = "UPPERCASE")]
176pub struct NotifiersResponse {
177    pub websocket: bool,
178}
179
180#[cfg(test)]
181mod tests {
182    use crate::program::ProgramRequest;
183
184    use super::*;
185
186    #[test]
187    fn parse_subscription_request() {
188        let example = r#"{
189  "clientName": "myClient",
190  "programID": "44",
191  "objectOperations": [
192    {
193      "callbackUrl": "https://myserver.com/event_callbacks",
194      "operations": [
195        "CREATE",
196        "UPDATE"
197      ],
198      "objects": [
199        "EVENT"
200      ]
201    },
202    {
203      "callbackUrl": "https://myserver.com/program_callbacks",
204      "operations": [
205        "CREATE",
206        "UPDATE"
207      ],
208      "objects": [
209        "PROGRAM"
210      ]
211    }
212  ]
213}"#;
214        assert_eq!(
215            serde_json::from_str::<SubscriptionRequest>(example).unwrap(),
216            SubscriptionRequest {
217                client_name: "myClient".to_owned(),
218                program_id: Some("44".parse().unwrap()),
219                object_operations: vec![
220                    SubscriptionObjectOperation {
221                        objects: vec![ObjectType::Event],
222                        operations: vec![Operation::Create, Operation::Update],
223                        mechanism: NotificationMechanism::Webhook,
224                        callback_url: Some("https://myserver.com/event_callbacks".to_owned()),
225                        bearer_token: None,
226                    },
227                    SubscriptionObjectOperation {
228                        objects: vec![ObjectType::Program],
229                        operations: vec![Operation::Create, Operation::Update],
230                        mechanism: NotificationMechanism::Webhook,
231                        callback_url: Some("https://myserver.com/program_callbacks".to_owned()),
232                        bearer_token: None,
233                    }
234                ],
235                // targets: vec![],
236            }
237        );
238    }
239
240    #[test]
241    fn parse_notification() {
242        let example = r#"{
243  "id": "100",
244  "objectType": "PROGRAM",
245  "operation": "UPDATE",
246  "object": {
247    "bindingEvents": false,
248    "createdDateTime": "2023-06-15T15:51:29.000Z",
249    "modificationDateTime": "2023-06-15T15:51:29.000Z",
250    "id": "0",
251    "localPrice": false,
252    "objectType": "PROGRAM",
253    "programName": "myProgram"
254  }
255}"#;
256        assert_eq!(
257            serde_json::from_str::<Notification>(example).unwrap(),
258            Notification {
259                id: "100".parse().unwrap(),
260                operation: Operation::Update,
261                object: AnyObject::Program(Program {
262                    id: "0".parse().unwrap(),
263                    created_date_time: "2023-06-15T15:51:29.000Z".parse().unwrap(),
264                    modification_date_time: "2023-06-15T15:51:29.000Z".parse().unwrap(),
265                    content: ProgramRequest {
266                        program_name: "myProgram".to_owned(),
267                        interval_period: None,
268                        program_descriptions: None,
269                        payload_descriptors: None,
270                        attributes: None,
271                        targets: vec![],
272                    }
273                }),
274                // targets: vec![],
275            }
276        );
277    }
278}