dce-hyper 1.0.0

A http routable protocol implementation for dce-router
Documentation
use std::any::Any;
use std::collections::{HashMap, HashSet};
use std::convert::Infallible;
use std::fmt::Debug;
use std::ops::{Deref, DerefMut};
use std::sync::Arc;
use async_trait::async_trait;
use http_body_util::{combinators::BoxBody, BodyExt, Empty, Full};
use hyper::body::{Bytes, Incoming};
use hyper::{Method, Request, Response, StatusCode};
#[allow(unused)]
use hyper::header::{COOKIE, HeaderValue};
use dce_router::protocol::{Meta, RoutableProtocol};
use dce_router::request::{Context, Request as DceRequest, Response as DceResponse};
use dce_router::router::Router;
use dce_router::serializer::Serialized;
use dce_util::mixed::{DceErr, DceResult};
use dce_router::api::Method as DceMethod;

pub type HttpRaw<'a> = DceRequest<'a, HyperHttpProtocol, (), ()>;
pub type HttpGet<'a, Dto> = DceRequest<'a, HyperHttpProtocol, (), Dto>;
pub type HttpSame<'a, Dto> = DceRequest<'a, HyperHttpProtocol, Dto, Dto>;
pub type Http<'a, ReqDto, RespDto> = DceRequest<'a, HyperHttpProtocol, ReqDto, RespDto>;

#[derive(Debug)]
pub struct HyperHttpProtocol {
    meta: Meta<Request<Incoming>, Response<BoxBody<Bytes, Infallible>>>,
}

impl HyperHttpProtocol {
    pub async fn route(
        self,
        router: Arc<Router<Self>>,
        context_data: HashMap<String, Box<dyn Any + Send>>,
    ) -> Result<Response<BoxBody<Bytes, Infallible>>, Infallible> {
        Self::handle(self, router, context_data).await.ok_or_else(|| unreachable!("http route should always return Some(Resp)"))
    }
}

impl From<Request<Incoming>> for HyperHttpProtocol {
    fn from(value: Request<Incoming>) -> Self {
        Self { meta: Meta::new(value, Default::default()) }
    }
}

impl Into<Response<BoxBody<Bytes, Infallible>>> for HyperHttpProtocol {
    fn into(mut self) -> Response<BoxBody<Bytes, Infallible>> {
        let resp = self.resp_mut().take();
        #[allow(unused_mut)]
        let mut resp = match resp {
            None => Response::new(Empty::new().boxed()),
            Some(DceResponse::Serialized(sd)) => self.pack_resp(sd),
            Some(DceResponse::Raw(rr)) => rr,
        };
        #[cfg(feature = "session")]
        if let Some(resp_sid) = self.get_resp_sid() {
            resp.headers_mut().insert("X-Session-Id", HeaderValue::from_str(resp_sid.as_str()).unwrap());
        }
        resp
    }
}

impl Deref for HyperHttpProtocol {
    type Target = Meta<Request<Incoming>, Response<BoxBody<Bytes, Infallible>>>;

    fn deref(&self) -> &Self::Target {
        &self.meta
    }
}

impl DerefMut for HyperHttpProtocol {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.meta
    }
}

#[async_trait]
impl RoutableProtocol for HyperHttpProtocol {
    type Req = Request<Incoming>;
    type Resp = Response<BoxBody<Bytes, Infallible>>;

    async fn body(&mut self) -> DceResult<Serialized> {
        let req = self.req_mut().take().ok_or_else(|| DceErr::closed0("Empty request"))?;
        Ok(Serialized::Bytes(req.collect().await.or_else(DceErr::closed0_wrap)?.to_bytes()))
    }

    fn pack_resp(&self, serialized: Serialized) -> Self::Resp {
        Response::new(match serialized {
            Serialized::String(str) => Full::from(str).boxed(),
            Serialized::Bytes(bytes) => Full::from(bytes).boxed(),
        })
    }

    fn path(&self) -> &str {
        self.req().map_or("", |r| r.uri().path().trim_start_matches('/'))
    }

    fn handle_result(self, result: DceResult<()>, _: &mut Context<Self>) -> Option<Self::Resp> {
        Self::try_print_err(&result);
        Some(match result {
            Ok(_) => self.into(),
            Err(err) => {
                let code = err.value().code;
                let is_openly = matches!(err, DceErr::Openly(_));
                let mut resp = self.err_into(err);
                if is_openly && code < 600 {
                    *resp.status_mut() = StatusCode::from_u16(code as u16).unwrap_or(StatusCode::SERVICE_UNAVAILABLE);
                }
                resp
            },
        })
    }

    #[cfg(feature = "session")]
    fn sid(&self) -> Option<&str> {
        self.req().map_or(None, |r| r.headers().get("X-Session-Id").map(|v| v.to_str().unwrap())

            .or_else(|| r.headers().get(COOKIE).iter().find_map(|v| {
                let mut cookies = v.to_str().unwrap_or("").split(';');
                cookies.find_map(|kv| if let Some(index) = kv.find("session_id=") { Some(&kv[index + 11 ..]) } else { None } )
            })))
    }

    fn parse_api_method(prop_mapping: &mut HashMap<&'static str, Box<dyn Any + Send + Sync>>) -> Option<Box<dyn DceMethod<Self> + Send + Sync>> {
        Self::parse_http_method(prop_mapping)
    }
}


impl HttpProtocol for HyperHttpProtocol {}

impl HttpMethodGetter for HyperHttpProtocol {
    fn method(&self) -> &Method {
        self.req().map_or_else(|_| &Method::GET, |r| r.method())
    }
}

pub trait HttpProtocol: RoutableProtocol + HttpMethodGetter {
    fn parse_http_method(prop_mapping: &mut HashMap<&'static str, Box<dyn Any + Send + Sync>>) -> Option<Box<dyn DceMethod<Self> + Send + Sync>> {
        Some(Box::new(prop_mapping.remove("method").map(|ms| if ms.is::<Method>() {
            ms.downcast::<Method>().map(|m| HashSet::from([*m])).ok()
        } else {
            ms.downcast::<Vec<Method>>().map(|m| m.into_iter().collect::<HashSet<_>>()).ok()
        }).unwrap_or_else(|| Some(HashSet::from([Method::GET, Method::HEAD, Method::OPTIONS]))).map(HttpMethodSet)?))
    }
}

#[allow(non_snake_case, non_upper_case_globals)]
pub mod HttpMethod {
    use hyper::Method;

    pub const Get: Method = Method::GET;
    pub const Post: Method = Method::POST;
    pub const Put: Method = Method::PUT;
    pub const Delete: Method = Method::DELETE;
    pub const Head: Method = Method::HEAD;
    pub const Options: Method = Method::OPTIONS;
    pub const Connect: Method = Method::CONNECT;
    pub const Patch: Method = Method::PATCH;
    pub const Trace: Method = Method::TRACE;
}

#[derive(Debug)]
pub struct HttpMethodSet(HashSet<Method>);

impl<T: HttpMethodGetter> DceMethod<T> for HttpMethodSet {
    fn to_string(&self) -> String {
        format!("[{}]", self.0.iter().map(|m| m.to_string()).fold(String::new(), |a, b| format!("{a}, {b}")))
    }

    fn req_match(&self, raw: &T) -> bool {
        self.0.contains(raw.method())
    }
}

pub trait HttpMethodGetter {
    fn method(&self) -> &Method;
}