use std::{convert::Infallible, sync::Arc}; mod permission; mod sales_person; mod slot; use axum::{body::Body, response::Response, Router}; use thiserror::Error; use uuid::Uuid; // TODO: In prod, it must be a different type than in dev mode. type Context = (); pub struct RoString(Arc, bool); impl http_body::Body for RoString { type Data = bytes::Bytes; type Error = Infallible; fn poll_frame( mut self: std::pin::Pin<&mut Self>, _cx: &mut std::task::Context<'_>, ) -> std::task::Poll, Self::Error>>> { std::task::Poll::Ready(if self.1 { None } else { self.1 = true; Some(Ok(http_body::Frame::data(bytes::Bytes::copy_from_slice( self.0.as_bytes(), )))) }) } fn is_end_stream(&self) -> bool { self.1 } } impl From> for RoString { fn from(s: Arc) -> Self { RoString(s, false) } } impl From for Response { fn from(s: RoString) -> Self { Response::builder().status(200).body(Body::new(s)).unwrap() } } #[derive(Debug, Error)] pub enum RestError { #[error("Service error")] ServiceError(#[from] service::ServiceError), #[error("Inconsistent id. Got {0} in path but {1} in body")] InconsistentId(Uuid, Uuid), } fn error_handler(result: Result) -> Response { match result { Ok(response) => response, Err(err @ RestError::InconsistentId(_, _)) => Response::builder() .status(400) .body(Body::new(err.to_string())) .unwrap(), Err(RestError::ServiceError(service::ServiceError::Forbidden)) => { Response::builder().status(403).body(Body::empty()).unwrap() } Err(RestError::ServiceError(service::ServiceError::DatabaseQueryError(e))) => { Response::builder() .status(500) .body(Body::new(e.to_string())) .unwrap() } Err(RestError::ServiceError(service::ServiceError::EntityAlreadyExists(id))) => { Response::builder() .status(409) .body(Body::new(id.to_string())) .unwrap() } Err(RestError::ServiceError(service::ServiceError::EntityNotFound(id))) => { Response::builder() .status(404) .body(Body::new(id.to_string())) .unwrap() } Err(RestError::ServiceError(err @ service::ServiceError::EntityConflicts(_, _, _))) => { Response::builder() .status(409) .body(Body::new(err.to_string())) .unwrap() } Err(RestError::ServiceError(err @ service::ServiceError::ValidationError(_))) => { Response::builder() .status(422) .body(Body::new(err.to_string())) .unwrap() } Err(RestError::ServiceError(err @ service::ServiceError::IdSetOnCreate)) => { Response::builder() .status(422) .body(Body::new(err.to_string())) .unwrap() } Err(RestError::ServiceError(err @ service::ServiceError::VersionSetOnCreate)) => { Response::builder() .status(422) .body(Body::new(err.to_string())) .unwrap() } Err(RestError::ServiceError(err @ service::ServiceError::OverlappingTimeRange)) => { Response::builder() .status(409) .body(Body::new(err.to_string())) .unwrap() } Err(RestError::ServiceError(err @ service::ServiceError::TimeOrderWrong(_, _))) => { Response::builder() .status(422) .body(Body::new(err.to_string())) .unwrap() } Err(RestError::ServiceError(err @ service::ServiceError::DateOrderWrong(_, _))) => { Response::builder() .status(422) .body(Body::new(err.to_string())) .unwrap() } } } pub trait RestStateDef: Clone + Send + Sync + 'static { type PermissionService: service::PermissionService + Send + Sync + 'static; type SlotService: service::slot::SlotService + Send + Sync + 'static; type SalesPersonService: service::sales_person::SalesPersonService + Send + Sync + 'static; fn permission_service(&self) -> Arc; fn slot_service(&self) -> Arc; fn sales_person_service(&self) -> Arc; } pub async fn start_server(rest_state: RestState) { let app = Router::new() .nest("/permission", permission::generate_route()) .nest("/slot", slot::generate_route()) .nest("/sales-person", sales_person::generate_route()) .with_state(rest_state); let listener = tokio::net::TcpListener::bind("127.0.0.1:3000") .await .expect("Could not bind server"); axum::serve(listener, app) .await .expect("Could not start server"); }