Skip to main content

haste_fhir_client/
axum.rs

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