use anyhow::Error;
use hyper::{Body, Request, Response};
use meio::handlers::Interact;
use meio::prelude::{ActionHandler, Actor, Address, Interaction};
use serde::{de::DeserializeOwned, Deserialize};
use slab::Slab;
use std::future::Future;
use std::net::SocketAddr;
use std::ops::Deref;
use std::pin::Pin;
use std::sync::Arc;
use thiserror::Error;
use tokio::sync::RwLock;
pub type BoxedRoute = Box<dyn Route>;
#[derive(Clone, Default)]
pub(super) struct RoutingTable {
routes: Arc<RwLock<Slab<BoxedRoute>>>,
}
impl RoutingTable {
pub async fn insert_route(&mut self, route: BoxedRoute) {
let mut routes = self.routes.write().await;
routes.insert(route);
}
pub async fn routes(&self) -> impl Deref<Target = Slab<BoxedRoute>> + '_ {
self.routes.read().await
}
}
#[derive(Default, Deserialize)]
pub struct NoParameters {}
#[derive(Debug, Error)]
#[error("route error [path = {path}, query = {query}]: {reason}")]
pub struct RouteError {
pub path: String,
pub query: String,
pub reason: String,
}
impl RouteError {
pub fn new(path: impl ToString, query: impl ToString, reason: impl ToString) -> Self {
Self {
path: path.to_string(),
query: query.to_string(),
reason: reason.to_string(),
}
}
}
pub trait DirectPath: Sized + Send + Sync + 'static {
type Output: DeserializeOwned + Send;
type Parameter;
fn paths() -> &'static [&'static str];
}
impl<T> FromRequest for T
where
T: DirectPath,
{
type Output = <T as DirectPath>::Output;
fn from_request(&self, request: &Request<Body>) -> Option<Result<Self::Output, Error>> {
let uri = request.uri();
let path = uri.path();
if Self::paths().iter().any(|p| p == &path) {
let query = uri.query().unwrap_or("");
let output =
serde_qs::from_str(query).map_err(|err| RouteError::new(path, query, err).into());
Some(output)
} else {
None
}
}
}
pub trait FromRequest: Sized + Send + Sync + 'static {
type Output: Send;
fn from_request(&self, request: &Request<Body>) -> Option<Result<Self::Output, Error>>;
}
pub struct Req<T: FromRequest> {
pub addr: SocketAddr,
pub data: T::Output,
pub body: Body,
}
impl<T: FromRequest> Interaction for Req<T> {
type Output = Response<Body>;
}
pub type RouteResult =
Result<Pin<Box<dyn Future<Output = Result<Response<Body>, Error>> + Send>>, Request<Body>>;
pub trait Route: Send + Sync + 'static {
fn try_route(&self, addr: &SocketAddr, request: Request<Body>) -> RouteResult;
}
pub struct WebRoute<E, A>
where
A: Actor,
{
extractor: E,
address: Address<A>,
}
impl<E, A> WebRoute<E, A>
where
A: Actor,
{
pub fn new(extractor: E, address: Address<A>) -> Self {
Self { extractor, address }
}
}
impl<E, A> Route for WebRoute<E, A>
where
E: FromRequest,
A: Actor + ActionHandler<Interact<Req<E>>>,
{
fn try_route(&self, addr: &SocketAddr, request: Request<Body>) -> RouteResult {
match self.extractor.from_request(&request) {
Some(Ok(data)) => {
let msg = Req {
addr: *addr,
data,
body: request.into_body(),
};
let fut = self.address.interact(msg).recv();
Ok(Box::pin(fut))
}
None => Err(request),
Some(Err(err)) => {
let fut = async move { Err(err) };
Ok(Box::pin(fut))
}
}
}
}