use crate::{
App, RequestParam, RequestParams,
error::ErrorExt,
http::{self, content::Accept},
mime,
request::best_response_type,
route::{self, Handler, RouteError},
};
use async_std::sync::Arc;
use futures::future::{BoxFuture, FutureExt};
use vbs::version::StaticVersionType;
pub(crate) trait ErrorHandler<Error>:
Fn(&Accept, &Error, &mut tide::Response) + Send + Sync
{
}
impl<Error, F> ErrorHandler<Error> for F where
F: Fn(&Accept, &Error, &mut tide::Response) + Send + Sync
{
}
pub(crate) fn error_handler<Error, VER>() -> Arc<dyn ErrorHandler<Error>>
where
Error: crate::Error,
VER: StaticVersionType,
{
Arc::new(|accept, error, res| {
let (body, content_type) = route::response_body::<_, Error, VER>(accept, &error)
.unwrap_or_else(|_| (error.to_string().into(), mime::PLAIN));
res.set_body(body);
res.set_content_type(content_type);
})
}
pub(crate) struct AddErrorBody<E> {
handler: Arc<dyn ErrorHandler<E>>,
}
impl<E> AddErrorBody<E> {
pub(crate) fn new(handler: Arc<dyn ErrorHandler<E>>) -> Self {
Self { handler }
}
pub(crate) fn with_version<VER>() -> Self
where
E: crate::Error,
VER: StaticVersionType,
{
Self::new(error_handler::<E, VER>())
}
}
impl<T, E> tide::Middleware<T> for AddErrorBody<E>
where
T: Clone + Send + Sync + 'static,
E: crate::Error,
{
fn handle<'a, 'b, 't>(
&'a self,
req: tide::Request<T>,
next: tide::Next<'b, T>,
) -> BoxFuture<'t, tide::Result>
where
'a: 't,
'b: 't,
Self: 't,
{
async {
let accept = RequestParams::accept_from_headers(&req)?;
let mut res = next.run(req).await;
if let Some(error) = res.take_error() {
let error = E::from_server_error(error);
tracing::info!("responding with error: {}", error);
(self.handler)(&accept, &error, &mut res);
Ok(res)
} else {
Ok(res)
}
}
.boxed()
}
}
pub(crate) struct MetricsMiddleware {
route: String,
api: Vec<String>,
api_version: u64,
}
impl MetricsMiddleware {
pub(crate) fn new(route: String, api: Vec<String>, api_version: u64) -> Self {
Self {
route,
api,
api_version,
}
}
}
impl<State, Error> tide::Middleware<Arc<App<State, Error>>> for MetricsMiddleware
where
State: Send + Sync + 'static,
Error: crate::Error + Send + Sync + 'static,
{
fn handle<'a, 'b, 't>(
&'a self,
req: tide::Request<Arc<App<State, Error>>>,
next: tide::Next<'b, Arc<App<State, Error>>>,
) -> BoxFuture<'t, tide::Result>
where
'a: 't,
'b: 't,
Self: 't,
{
let route = self.route.clone();
let api = self.api.clone();
let version = self.api_version;
async move {
if req.method() != http::Method::Get {
return Ok(next.run(req).await);
}
let accept = RequestParams::accept_from_headers(&req)?;
let reponse_ty =
best_response_type(&accept, &[mime::PLAIN, mime::JSON, mime::BYTE_STREAM])?;
if reponse_ty != mime::PLAIN {
return Ok(next.run(req).await);
}
let route = &req.state().clone().modules[&api].versions[&version][&route];
let state = &*req.state().clone().state;
let req = request_params(req, route.params()).await?;
route
.handle(req, state)
.await
.map_err(|err| match err {
RouteError::AppSpecific(err) => err,
_ => Error::from_route_error(err),
})
.map_err(|err| err.into_tide_error())
}
.boxed()
}
}
pub(crate) async fn request_params<State, Error: crate::Error>(
req: tide::Request<Arc<App<State, Error>>>,
params: &[RequestParam],
) -> Result<RequestParams, tide::Error> {
RequestParams::new(req, params)
.await
.map_err(|err| Error::from_request_error(err).into_tide_error())
}