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