helios_subscriptions/notification/
builder.rs1use chrono::Utc;
6use serde_json::json;
7use uuid::Uuid;
8
9use crate::error::SubscriptionError;
10use crate::manager::{ActiveSubscription, PayloadContent};
11use crate::notification::{NotificationEventData, NotificationType};
12
13pub struct NotificationBundleBuilder<'a> {
15 subscription: &'a ActiveSubscription,
16 notification_type: NotificationType,
17 base_url: &'a str,
18}
19
20impl<'a> NotificationBundleBuilder<'a> {
21 pub fn new(
22 subscription: &'a ActiveSubscription,
23 notification_type: NotificationType,
24 base_url: &'a str,
25 ) -> Self {
26 Self {
27 subscription,
28 notification_type,
29 base_url,
30 }
31 }
32
33 #[cfg(feature = "R4")]
38 pub fn build_r4(
39 &self,
40 events: &[NotificationEventData],
41 resources: &[serde_json::Value],
42 ) -> Result<serde_json::Value, SubscriptionError> {
43 let mut entries = Vec::new();
44
45 let status_parameters = self.build_r4_status_parameters(events);
47 entries.push(json!({
48 "fullUrl": format!("urn:uuid:{}", Uuid::new_v4()),
49 "resource": status_parameters,
50 "request": {
51 "method": "GET",
52 "url": format!("Subscription/{}/$status", self.subscription.id)
53 },
54 "response": {
55 "status": "200"
56 }
57 }));
58
59 self.add_payload_entries(&mut entries, events, resources);
61
62 Ok(json!({
63 "resourceType": "Bundle",
64 "id": Uuid::new_v4().to_string(),
65 "type": "history",
66 "timestamp": Utc::now().to_rfc3339(),
67 "entry": entries
68 }))
69 }
70
71 #[cfg(feature = "R4")]
73 fn build_r4_status_parameters(&self, events: &[NotificationEventData]) -> serde_json::Value {
74 let mut parameters = vec![
75 json!({
76 "name": "subscription",
77 "valueReference": {
78 "reference": format!("Subscription/{}", self.subscription.id)
79 }
80 }),
81 json!({
82 "name": "topic",
83 "valueCanonical": self.subscription.topic_url
84 }),
85 json!({
86 "name": "status",
87 "valueCode": self.subscription.status.as_fhir_str()
88 }),
89 json!({
90 "name": "type",
91 "valueCode": self.notification_type.as_fhir_str()
92 }),
93 json!({
94 "name": "events-since-subscription-start",
95 "valueString": self.subscription.events_since_start.to_string()
96 }),
97 ];
98
99 for event in events {
101 let event_parts = vec![
102 json!({
103 "name": "event-number",
104 "valueString": event.event_number.to_string()
105 }),
106 json!({
107 "name": "timestamp",
108 "valueInstant": event.timestamp.to_rfc3339()
109 }),
110 json!({
111 "name": "focus",
112 "valueReference": {
113 "reference": &event.focus_reference
114 }
115 }),
116 ];
117
118 parameters.push(json!({
119 "name": "notification-event",
120 "part": event_parts
121 }));
122 }
123
124 json!({
125 "resourceType": "Parameters",
126 "parameter": parameters
127 })
128 }
129
130 pub fn build_native(
136 &self,
137 events: &[NotificationEventData],
138 resources: &[serde_json::Value],
139 ) -> Result<serde_json::Value, SubscriptionError> {
140 let mut entries = Vec::new();
141
142 let status = self.build_native_status(events);
144 entries.push(json!({
145 "fullUrl": format!("urn:uuid:{}", Uuid::new_v4()),
146 "resource": status,
147 "request": {
148 "method": "GET",
149 "url": format!("Subscription/{}/$status", self.subscription.id)
150 },
151 "response": {
152 "status": "200"
153 }
154 }));
155
156 self.add_payload_entries(&mut entries, events, resources);
158
159 let bundle_type = self.bundle_type();
160
161 Ok(json!({
162 "resourceType": "Bundle",
163 "id": Uuid::new_v4().to_string(),
164 "type": bundle_type,
165 "timestamp": Utc::now().to_rfc3339(),
166 "entry": entries
167 }))
168 }
169
170 fn build_native_status(&self, events: &[NotificationEventData]) -> serde_json::Value {
172 let mut notification_events = Vec::new();
173 for event in events {
174 notification_events.push(json!({
175 "eventNumber": event.event_number,
176 "timestamp": event.timestamp.to_rfc3339(),
177 "focus": {
178 "reference": &event.focus_reference
179 }
180 }));
181 }
182
183 let mut status = json!({
184 "resourceType": "SubscriptionStatus",
185 "status": self.subscription.status.as_fhir_str(),
186 "type": self.notification_type.as_fhir_str(),
187 "eventsSinceSubscriptionStart": self.subscription.events_since_start,
188 "subscription": {
189 "reference": format!("Subscription/{}", self.subscription.id)
190 },
191 "topic": self.subscription.topic_url
192 });
193
194 if !notification_events.is_empty() {
195 status["notificationEvent"] = json!(notification_events);
196 }
197
198 status
199 }
200
201 fn add_payload_entries(
203 &self,
204 entries: &mut Vec<serde_json::Value>,
205 events: &[NotificationEventData],
206 resources: &[serde_json::Value],
207 ) {
208 match self.subscription.channel.payload_content {
209 PayloadContent::Empty => {
210 }
212 PayloadContent::IdOnly => {
213 for event in events {
214 entries.push(json!({
215 "fullUrl": format!("{}/{}", self.base_url, event.focus_reference),
216 "request": {
217 "method": "GET",
218 "url": &event.focus_reference
219 },
220 "response": {
221 "status": "200"
222 }
223 }));
224 }
225 }
226 PayloadContent::FullResource => {
227 for (i, event) in events.iter().enumerate() {
228 let mut entry = json!({
229 "fullUrl": format!("{}/{}", self.base_url, event.focus_reference),
230 "request": {
231 "method": "GET",
232 "url": &event.focus_reference
233 },
234 "response": {
235 "status": "200"
236 }
237 });
238
239 if let Some(resource) = resources.get(i) {
240 entry["resource"] = resource.clone();
241 }
242
243 entries.push(entry);
244 }
245 }
246 }
247 }
248
249 fn bundle_type(&self) -> &'static str {
251 match self.subscription.fhir_version {
252 #[cfg(feature = "R4")]
253 helios_fhir::FhirVersion::R4 => "history",
254
255 #[cfg(feature = "R4B")]
256 helios_fhir::FhirVersion::R4B => "history",
257
258 #[allow(unreachable_patterns)]
260 _ => "subscription-notification",
261 }
262 }
263}