use std::sync::Arc;
use axum::extract::RawPathParams;
use axum::extract::rejection::RawPathParamsRejection;
use axum::response::IntoResponse as _;
use http::header::{ALLOW, CONTENT_TYPE};
use http::{HeaderMap, HeaderValue, StatusCode};
use super::decode::PathValues;
use super::generated::admin::server::AdminApi;
use super::generated::definition::server::DefinitionApi;
use super::generated::demographic::server::DemographicApi;
use super::generated::ehr::server::EhrApi;
use super::generated::query::server::QueryApi;
use super::generated::system::server::SystemApi;
use super::generated::{admin, definition, demographic, ehr, query, system};
use super::routes::{Lookup, lookup};
use super::runtime::{
ApiError, HeaderError, Refusal, ResponseHeaders, error_response, merge_headers,
};
const CANONICAL_JSON: &str = "application/json";
pub(crate) fn path_captures(
captured: Result<RawPathParams, RawPathParamsRejection>,
names: &[(&str, &'static str)],
) -> Result<PathValues, ApiError> {
let captured = captured.map_err(|rejection| {
ApiError::BadRequest(format!("a path segment is not valid: {rejection}"))
})?;
Ok(PathValues::new(
captured
.iter()
.filter_map(|(capture, value)| {
names
.iter()
.find(|(c, _)| *c == capture)
.map(|(_, name)| (*name, value.to_owned()))
})
.collect(),
))
}
pub(crate) struct Reply {
status: StatusCode,
headers: HeaderMap,
body: Vec<u8>,
}
impl Reply {
pub(crate) fn new(status: StatusCode) -> Self {
Self {
status,
headers: HeaderMap::new(),
body: Vec::new(),
}
}
pub(crate) fn json<B: serde::Serialize + ?Sized>(&mut self, body: &B) -> Result<(), ApiError> {
self.body = match serde_json::to_vec(body) {
Ok(bytes) => bytes,
Err(_) => {
return Err(ApiError::Internal(
"the answer body does not serialize".to_owned(),
));
}
};
self.headers
.insert(CONTENT_TYPE, HeaderValue::from_static(CANONICAL_JSON));
Ok(())
}
pub(crate) fn raw(&mut self, body: Vec<u8>) {
self.body = body;
}
pub(crate) fn headers(&mut self, headers: impl ResponseHeaders) -> Result<(), HeaderError> {
merge_headers(&mut self.headers, headers.into_header_map()?);
Ok(())
}
pub(crate) fn finish(self) -> axum::response::Response {
let mut response = axum::response::Response::new(axum::body::Body::from(self.body));
*response.status_mut() = self.status;
*response.headers_mut() = self.headers;
response
}
}
pub(crate) fn respond(
served: Result<axum::response::Response, Refusal>,
) -> axum::response::Response {
match served {
Ok(response) => response,
Err(refusal) => refusal.into_response(),
}
}
pub fn router<S>(api: Arc<S>) -> axum::Router
where
S: AdminApi + DefinitionApi + DemographicApi + EhrApi + QueryApi + SystemApi,
S: Send + Sync + 'static,
{
with_fallbacks(
admin::server::router(Arc::clone(&api))
.merge(definition::server::router(Arc::clone(&api)))
.merge(demographic::server::router(Arc::clone(&api)))
.merge(ehr::server::router(Arc::clone(&api)))
.merge(query::server::router(Arc::clone(&api)))
.merge(system::server::router(api)),
)
}
pub fn with_fallbacks(router: axum::Router) -> axum::Router {
router
.fallback(not_found)
.method_not_allowed_fallback(method_not_allowed)
}
async fn not_found(uri: http::Uri) -> axum::response::Response {
error_response(
StatusCode::NOT_FOUND,
format!("no ITS-REST operation has the path `{}`", uri.path()),
Vec::new(),
)
}
async fn method_not_allowed(method: http::Method, uri: http::Uri) -> axum::response::Response {
let mut response = error_response(
StatusCode::METHOD_NOT_ALLOWED,
format!("the operations at `{}` do not take {method}", uri.path()),
Vec::new(),
);
if let Lookup::MethodNotAllowed { allowed } = lookup(&method, uri.path())
&& let Ok(value) = HeaderValue::from_str(&allowed.join(", "))
{
response.headers_mut().insert(ALLOW, value);
}
response
}