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#[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}
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#[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 }
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 }
290 );
291 }
292}