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#[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 ResourceGroup(ResourceGroup),
161}
162
163impl AnyObject {
164 pub fn id(&self) -> Identifier {
165 match self {
166 AnyObject::Program(program) => program.id.0.clone(),
167 AnyObject::Report(report) => report.id.0.clone(),
168 AnyObject::Event(event) => event.id.0.clone(),
169 AnyObject::Subscription(subscription) => subscription.id.0.clone(),
170 AnyObject::Ven(ven) => ven.id.0.clone(),
171 AnyObject::Resource(resource) => resource.id.0.clone(),
172 AnyObject::ResourceGroup(resource_group) => resource_group.id.0.clone(),
173 }
174 }
175
176 pub fn kind(&self) -> ObjectType {
177 match self {
178 AnyObject::Program(_) => ObjectType::Program,
179 AnyObject::Report(_) => ObjectType::Report,
180 AnyObject::Event(_) => ObjectType::Event,
181 AnyObject::Subscription(_) => ObjectType::Subscription,
182 AnyObject::Ven(_) => ObjectType::Ven,
183 AnyObject::Resource(_) => ObjectType::Resource,
184 AnyObject::ResourceGroup(_) => ObjectType::ResourceGroup,
185 }
186 }
187}
188
189#[skip_serializing_none]
191#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Validate)]
192#[serde(rename_all = "UPPERCASE")]
193pub struct NotifiersResponse {
194 pub websocket: bool,
195}
196
197#[cfg(test)]
198mod tests {
199 use crate::program::ProgramRequest;
200
201 use super::*;
202
203 #[test]
204 fn parse_subscription_request() {
205 let example = r#"{
206 "clientName": "myClient",
207 "programID": "44",
208 "objectOperations": [
209 {
210 "callbackUrl": "https://myserver.com/event_callbacks",
211 "operations": [
212 "CREATE",
213 "UPDATE"
214 ],
215 "objects": [
216 "EVENT"
217 ]
218 },
219 {
220 "callbackUrl": "https://myserver.com/program_callbacks",
221 "operations": [
222 "CREATE",
223 "UPDATE"
224 ],
225 "objects": [
226 "PROGRAM"
227 ]
228 }
229 ]
230}"#;
231 assert_eq!(
232 serde_json::from_str::<SubscriptionRequest>(example).unwrap(),
233 SubscriptionRequest {
234 client_name: "myClient".to_owned(),
235 program_id: Some("44".parse().unwrap()),
236 object_operations: vec![
237 SubscriptionObjectOperation {
238 objects: vec![ObjectType::Event],
239 operations: vec![Operation::Create, Operation::Update],
240 mechanism: NotificationMechanism::Webhook,
241 callback_url: Some("https://myserver.com/event_callbacks".to_owned()),
242 bearer_token: None,
243 },
244 SubscriptionObjectOperation {
245 objects: vec![ObjectType::Program],
246 operations: vec![Operation::Create, Operation::Update],
247 mechanism: NotificationMechanism::Webhook,
248 callback_url: Some("https://myserver.com/program_callbacks".to_owned()),
249 bearer_token: None,
250 }
251 ],
252 }
254 );
255 }
256
257 #[test]
258 fn parse_notification() {
259 let example = r#"{
260 "id": "100",
261 "objectType": "PROGRAM",
262 "operation": "UPDATE",
263 "object": {
264 "bindingEvents": false,
265 "createdDateTime": "2023-06-15T15:51:29.000Z",
266 "modificationDateTime": "2023-06-15T15:51:29.000Z",
267 "id": "0",
268 "localPrice": false,
269 "objectType": "PROGRAM",
270 "programName": "myProgram"
271 }
272}"#;
273 assert_eq!(
274 serde_json::from_str::<Notification>(example).unwrap(),
275 Notification {
276 id: "100".parse().unwrap(),
277 operation: Operation::Update,
278 object: AnyObject::Program(Program {
279 id: "0".parse().unwrap(),
280 created_date_time: "2023-06-15T15:51:29.000Z".parse().unwrap(),
281 modification_date_time: "2023-06-15T15:51:29.000Z".parse().unwrap(),
282 content: ProgramRequest {
283 program_name: "myProgram".to_owned(),
284 interval_period: None,
285 program_descriptions: None,
286 payload_descriptors: None,
287 attributes: None,
288 targets: vec![],
289 }
290 }),
291 }
293 );
294 }
295}