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 ClientId, Event, Identifier, IdentifierError, ObjectType, Program, Report, Ven,
13 program::ProgramId, resource::Resource, resource_group::ResourceGroup,
14};
15
16#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Validate)]
18#[serde(rename_all = "camelCase")]
19pub struct Subscription {
20 pub id: SubscriptionId,
22 #[serde(with = "crate::serde_rfc3339")]
24 pub created_date_time: DateTime<Utc>,
25 #[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#[skip_serializing_none]
38#[serde_as]
39#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Validate)]
40#[serde(rename_all = "camelCase")]
41pub struct SubscriptionRequest {
42 #[serde(deserialize_with = "crate::string_within_range_inclusive::<1, 128, _>")]
44 pub client_name: String,
45
46 #[serde(rename = "programID")]
48 pub program_id: Option<ProgramId>,
49
50 #[validate(length(min = 1, max = 15))]
52 pub object_operations: Vec<SubscriptionObjectOperation>,
53 }
58
59#[skip_serializing_none]
60#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Validate)]
61#[serde(rename_all = "camelCase")]
62pub struct SubscriptionObjectOperation {
63 pub objects: Vec<ObjectType>,
65
66 pub operations: Vec<Operation>,
68
69 #[serde(default)]
71 pub mechanism: NotificationMechanism,
72
73 pub callback_url: Option<String>,
75
76 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#[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#[skip_serializing_none]
124#[serde_as]
125#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Validate)]
126#[serde(rename_all = "camelCase")]
127pub struct Notification {
128 pub id: Identifier,
138
139 pub operation: Operation,
141
142 #[serde(flatten)]
144 pub object: AnyObject,
145 }
150
151#[skip_serializing_none]
152#[serde_as]
153#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Validate)]
154#[serde(rename_all = "camelCase")]
155pub struct MqttPushNotification {
156 #[serde(rename = "ID")]
157 pub id: Identifier,
158 #[serde(rename = "notificationID")]
159 pub notification_id: Identifier,
160 pub object_type: ObjectType,
161 pub operation: Operation,
162 #[serde(with = "crate::serde_rfc3339")]
163 pub notification_date_time: DateTime<Utc>,
164}
165
166#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
167#[serde(tag = "objectType", content = "object", rename_all = "UPPERCASE")]
168pub enum AnyObject {
169 Program(Program),
170 Report(Report),
171 Event(Event),
172 Subscription(Subscription),
173 Ven(Ven),
174 Resource(Resource),
175 ResourceGroup(ResourceGroup),
176}
177
178impl AnyObject {
179 pub fn id(&self) -> Identifier {
180 match self {
181 AnyObject::Program(program) => program.id.0.clone(),
182 AnyObject::Report(report) => report.id.0.clone(),
183 AnyObject::Event(event) => event.id.0.clone(),
184 AnyObject::Subscription(subscription) => subscription.id.0.clone(),
185 AnyObject::Ven(ven) => ven.id.0.clone(),
186 AnyObject::Resource(resource) => resource.id.0.clone(),
187 AnyObject::ResourceGroup(resource_group) => resource_group.id.0.clone(),
188 }
189 }
190
191 pub fn kind(&self) -> ObjectType {
192 match self {
193 AnyObject::Program(_) => ObjectType::Program,
194 AnyObject::Report(_) => ObjectType::Report,
195 AnyObject::Event(_) => ObjectType::Event,
196 AnyObject::Subscription(_) => ObjectType::Subscription,
197 AnyObject::Ven(_) => ObjectType::Ven,
198 AnyObject::Resource(_) => ObjectType::Resource,
199 AnyObject::ResourceGroup(_) => ObjectType::ResourceGroup,
200 }
201 }
202}
203
204#[skip_serializing_none]
206#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Validate)]
207#[serde(rename_all = "SCREAMING-KEBAB-CASE")]
208pub struct NotifiersResponse {
209 pub websocket: bool,
210 pub mqtt: Option<MqttNotifierBindingObject>,
211 pub push_mqtt: Option<MqttNotifierBindingObject>,
212}
213
214#[skip_serializing_none]
216#[serde_as]
217#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Validate)]
218#[serde(rename_all = "camelCase")]
219pub struct MqttNotifierBindingObject {
220 #[serde(rename = "URIS")]
222 pub uris: Vec<String>,
223 pub serialization: SerializationType,
225 pub authentication: MqttNotifierAuthentication,
226}
227
228#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
230#[serde(rename_all = "UPPERCASE", rename_all_fields = "camelCase")]
231#[serde(tag = "method")]
232pub enum MqttNotifierAuthentication {
233 Anonymous,
235 Oauth2BearerToken {
237 username: String,
239 },
240}
241
242#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
243#[serde(rename_all = "UPPERCASE")]
244pub enum SerializationType {
245 Json,
246}
247
248#[skip_serializing_none]
250#[serde_as]
251#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
252#[serde(rename_all = "camelCase")]
253pub struct NotifierTopicsResponse {
254 pub topics: NotifierOperationsTopics,
255}
256
257#[skip_serializing_none]
259#[serde_as]
260#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
261#[serde(rename_all = "UPPERCASE")]
262pub struct NotifierOperationsTopics {
263 pub create: Option<String>,
268 pub update: String,
270 pub delete: String,
272 pub all: Option<String>,
274}
275
276#[cfg(test)]
277mod tests {
278 use crate::program::ProgramRequest;
279
280 use super::*;
281
282 #[test]
283 fn parse_subscription_request() {
284 let example = r#"{
285 "clientName": "myClient",
286 "programID": "44",
287 "objectOperations": [
288 {
289 "callbackUrl": "https://myserver.com/event_callbacks",
290 "operations": [
291 "CREATE",
292 "UPDATE"
293 ],
294 "objects": [
295 "EVENT"
296 ]
297 },
298 {
299 "callbackUrl": "https://myserver.com/program_callbacks",
300 "operations": [
301 "CREATE",
302 "UPDATE"
303 ],
304 "objects": [
305 "PROGRAM"
306 ]
307 }
308 ]
309}"#;
310 assert_eq!(
311 serde_json::from_str::<SubscriptionRequest>(example).unwrap(),
312 SubscriptionRequest {
313 client_name: "myClient".to_owned(),
314 program_id: Some("44".parse().unwrap()),
315 object_operations: vec![
316 SubscriptionObjectOperation {
317 objects: vec![ObjectType::Event],
318 operations: vec![Operation::Create, Operation::Update],
319 mechanism: NotificationMechanism::Webhook,
320 callback_url: Some("https://myserver.com/event_callbacks".to_owned()),
321 bearer_token: None,
322 },
323 SubscriptionObjectOperation {
324 objects: vec![ObjectType::Program],
325 operations: vec![Operation::Create, Operation::Update],
326 mechanism: NotificationMechanism::Webhook,
327 callback_url: Some("https://myserver.com/program_callbacks".to_owned()),
328 bearer_token: None,
329 }
330 ],
331 }
333 );
334 }
335
336 #[test]
337 fn parse_notification() {
338 let example = r#"{
339 "id": "100",
340 "objectType": "PROGRAM",
341 "operation": "UPDATE",
342 "object": {
343 "bindingEvents": false,
344 "createdDateTime": "2023-06-15T15:51:29.000Z",
345 "modificationDateTime": "2023-06-15T15:51:29.000Z",
346 "id": "0",
347 "localPrice": false,
348 "objectType": "PROGRAM",
349 "programName": "myProgram"
350 }
351}"#;
352 assert_eq!(
353 serde_json::from_str::<Notification>(example).unwrap(),
354 Notification {
355 id: "100".parse().unwrap(),
356 operation: Operation::Update,
357 object: AnyObject::Program(Program {
358 id: "0".parse().unwrap(),
359 created_date_time: "2023-06-15T15:51:29.000Z".parse().unwrap(),
360 modification_date_time: "2023-06-15T15:51:29.000Z".parse().unwrap(),
361 content: ProgramRequest {
362 program_name: "myProgram".to_owned(),
363 interval_period: None,
364 program_descriptions: None,
365 payload_descriptors: None,
366 attributes: None,
367 targets: vec![],
368 }
369 }),
370 }
372 );
373 }
374}