boxy/src/services/matcher.rs
2025-08-02 20:43:23 +02:00

120 lines
3.1 KiB
Rust

use std::{pin::Pin, sync::Arc};
use async_trait::async_trait;
use http::request::Parts;
use hyper::{Request, StatusCode, body::Incoming, service::Service};
use tokio::net::TcpStream;
use crate::server::{GeneralResponse, TcpIntercept, custom_resp, default_response};
// The routes itself
#[async_trait]
pub trait Route<T>
where
T: Matcher,
{
fn matcher(&self, m: &T, req: &Request<Incoming>) -> bool;
async fn call(&self, m: &T, parts: Parts) -> Result<GeneralResponse, hyper::Error>;
}
// Matcher, essentially just a router that contains routes and some other features
#[async_trait]
pub trait Matcher: Clone + Send + Sync + 'static {
// Essentially a kind of "middleware", a universal matcher. If it doesn't match, it won't
// route.
async fn unimatch(
&mut self,
req: &Request<Incoming>,
) -> (bool, Option<Result<GeneralResponse, hyper::Error>>);
// Return list of routes associated with self matcher
fn retrieve(&self) -> Vec<Arc<dyn Route<Self> + Sync + Send>>;
// Wrap self into matcher service
fn service(self) -> MatcherService<Self> {
MatcherService::new(self)
}
// Do something with TCP stream
#[allow(unused_variables)]
fn stream(&mut self, stream: &TcpStream) {}
// Body parser - made universal for api server cause lazy
#[allow(unused_variables)]
async fn body(&mut self, body: Incoming) -> Option<Result<GeneralResponse, hyper::Error>> {
None
}
}
// Wrapper service, wraps matcher into a service
#[derive(Clone)]
pub struct MatcherService<T>
where
T: Matcher,
{
inner: T,
}
impl<T> MatcherService<T>
where
T: Matcher,
{
pub fn new(inner: T) -> Self {
Self { inner }
}
}
impl<T> Service<Request<Incoming>> for MatcherService<T>
where
T: Matcher,
{
type Response = GeneralResponse;
type Error = hyper::Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
fn call(&self, req: Request<Incoming>) -> Self::Future {
let mut matcher = self.inner.clone();
Box::pin(async move {
let unimatched = matcher.unimatch(&req).await;
if !unimatched.0 {
match unimatched.1 {
Some(x) => {
return x;
}
None => {
return Ok(custom_resp(
StatusCode::NOT_FOUND,
"Could not match route".to_string(),
)
.await);
}
}
}
for r in matcher.retrieve() {
if r.matcher(&matcher, &req) {
let (parts, body) = req.into_parts();
if let Some(resp) = matcher.body(body).await {
return resp;
}
return r.call(&matcher, parts).await;
}
}
Ok(default_response().await)
})
}
}
impl<T> TcpIntercept for MatcherService<T>
where
T: Matcher,
{
fn stream(&mut self, stream: &TcpStream) {
self.inner.stream(stream);
}
}