1use crate::request::{
2 DeleteResponse, FHIRAcceptedResponse, FHIRResponse, HistoryResponse, InvokeResponse,
3 SearchResponse,
4};
5use axum::response::IntoResponse;
6use haste_fhir_model::r4::generated::{
7 resources::Resource,
8 terminology::IssueType,
9 types::{FHIRId, FHIRInstant},
10};
11use haste_fhir_operation_error::OperationOutcomeError;
12use haste_reflect::MetaValue;
13use http::{HeaderMap, StatusCode};
14
15fn add_resource_headers(headers: &mut HeaderMap, resource: &Resource) {
16 let _id = resource
17 .get_field("id")
18 .and_then(|id| id.as_any().downcast_ref::<String>());
19
20 let meta = resource.get_field("meta");
21
22 let last_modified = meta
23 .and_then(|meta| meta.get_field("lastUpdated"))
24 .and_then(|lu| lu.as_any().downcast_ref::<FHIRInstant>())
25 .and_then(|lu| lu.value.as_ref());
26
27 let version_id = meta
28 .and_then(|meta| meta.get_field("versionId"))
29 .and_then(|vid| vid.as_any().downcast_ref::<FHIRId>())
30 .and_then(|vid| vid.value.as_ref());
31
32 if let Some(last_modified) = last_modified {
33 headers.insert(
34 axum::http::header::LAST_MODIFIED,
35 last_modified
36 .format("%a, %d %b %Y %H:%M:%S GMT")
37 .parse()
38 .unwrap(),
39 );
40 }
41 if let Some(version_id) = version_id {
42 headers.insert(
43 axum::http::header::ETAG,
44 format!("W/\"{version_id}\"").parse().unwrap(),
45 );
46 }
47}
48
49fn add_headers(response: &FHIRResponse) -> HeaderMap {
50 let mut header = HeaderMap::new();
51 header.insert(
52 axum::http::header::CONTENT_TYPE,
53 "application/fhir+json; charset=utf-8".parse().unwrap(),
54 );
55 header.insert(
59 axum::http::header::CACHE_CONTROL,
60 "no-store".parse().unwrap(),
61 );
62
63 match response {
64 FHIRResponse::Create(resp) => {
65 add_resource_headers(&mut header, &resp.resource);
66 }
67 FHIRResponse::Read(resp) => {
68 if let Some(resource) = &resp.resource {
69 add_resource_headers(&mut header, resource);
70 }
71 }
72 FHIRResponse::VersionRead(resp) => {
73 add_resource_headers(&mut header, &resp.resource);
74 }
75 FHIRResponse::Update(resp) => {
76 add_resource_headers(&mut header, &resp.resource);
77 }
78 FHIRResponse::Patch(fhirpatch_response) => {
79 add_resource_headers(&mut header, &fhirpatch_response.resource);
80 }
81 _ => {}
82 }
83
84 header
85}
86
87impl IntoResponse for FHIRResponse {
88 fn into_response(self) -> axum::response::Response {
89 let header = add_headers(&self);
90
91 match self {
92 FHIRResponse::Create(response) => {
93 FHIRResponse::created_json(header, &response.resource)
94 }
95
96 FHIRResponse::Read(response) => match response.resource {
97 Some(resource) => FHIRResponse::ok_json(header, &resource),
98 None => FHIRResponse::not_found(),
99 },
100
101 FHIRResponse::VersionRead(response) => {
102 FHIRResponse::ok_json(header, &response.resource)
103 }
104
105 FHIRResponse::Update(response) => FHIRResponse::ok_json(header, &response.resource),
106
107 FHIRResponse::Capabilities(response) => {
108 FHIRResponse::ok_json(header, &response.capabilities)
109 }
110
111 FHIRResponse::History(history_response) => {
112 FHIRResponse::history_response(header, history_response)
113 }
114
115 FHIRResponse::Search(search_response) => {
116 FHIRResponse::search_response(header, search_response)
117 }
118
119 FHIRResponse::Batch(response) => FHIRResponse::ok_json(header, &response.resource),
120
121 FHIRResponse::Invoke(invoke_response) => {
122 FHIRResponse::invoke_response(header, invoke_response)
123 }
124
125 FHIRResponse::Delete(delete_response) => {
126 FHIRResponse::delete_response(header, delete_response)
127 }
128
129 FHIRResponse::Patch(response) => FHIRResponse::ok_json(header, &response.resource),
130
131 FHIRResponse::Transaction(response) => {
132 FHIRResponse::ok_json(header, &response.resource)
133 }
134
135 FHIRResponse::Accepted(response) => FHIRResponse::accepted(header, &response),
136 }
137 }
138}
139
140impl FHIRResponse {
141 fn json_response<T: serde::Serialize>(
142 status: StatusCode,
143 header: HeaderMap,
144 value: &T,
145 ) -> axum::response::Response {
146 (
147 status,
148 header,
149 serde_json::to_string(value).unwrap(),
151 )
152 .into_response()
153 }
154
155 fn ok_json<T: serde::Serialize>(header: HeaderMap, value: &T) -> axum::response::Response {
156 Self::json_response(StatusCode::OK, header, value)
157 }
158
159 fn created_json<T: serde::Serialize>(header: HeaderMap, value: &T) -> axum::response::Response {
160 Self::json_response(StatusCode::CREATED, header, value)
161 }
162
163 fn no_content(header: HeaderMap) -> axum::response::Response {
164 (StatusCode::NO_CONTENT, header, "").into_response()
165 }
166
167 fn accepted(
169 mut header: HeaderMap,
170 response: &FHIRAcceptedResponse,
171 ) -> axum::response::Response {
172 header.remove(axum::http::header::CONTENT_TYPE);
173 match response.content_location.parse() {
174 Ok(location) => {
175 header.insert(axum::http::header::CONTENT_LOCATION, location);
176 (StatusCode::ACCEPTED, header).into_response()
177 }
178 Err(_) => OperationOutcomeError::fatal(
179 IssueType::exception(),
180 "The job's status URL is not a valid header value.".to_string(),
181 )
182 .into_response(),
183 }
184 }
185
186 fn not_found() -> axum::response::Response {
187 OperationOutcomeError::error(IssueType::not_found(), "Resource not found.".to_string())
188 .into_response()
189 }
190
191 fn history_response(header: HeaderMap, response: HistoryResponse) -> axum::response::Response {
192 match response {
193 HistoryResponse::Instance(response) => Self::ok_json(header, &response.bundle),
194 HistoryResponse::Type(response) => Self::ok_json(header, &response.bundle),
195 HistoryResponse::System(response) => Self::ok_json(header, &response.bundle),
196 }
197 }
198
199 fn search_response(header: HeaderMap, response: SearchResponse) -> axum::response::Response {
200 match response {
201 SearchResponse::Type(response) => Self::ok_json(header, &response.bundle),
202 SearchResponse::System(response) => Self::ok_json(header, &response.bundle),
203 }
204 }
205
206 fn invoke_response(header: HeaderMap, response: InvokeResponse) -> axum::response::Response {
207 match response {
208 InvokeResponse::Instance(response) => Self::ok_json(header, &response.resource),
209 InvokeResponse::Type(response) => Self::ok_json(header, &response.resource),
210 InvokeResponse::System(response) => Self::ok_json(header, &response.resource),
211 }
212 }
213
214 fn delete_response(header: HeaderMap, response: DeleteResponse) -> axum::response::Response {
215 match response {
216 DeleteResponse::Instance(response) => Self::ok_json(header, &response.resource),
217 DeleteResponse::Type(_) | DeleteResponse::System(_) => Self::no_content(header),
218 }
219 }
220}