shors 0.13.0

Transport layer for cartridge + tarantool-module projects.
Documentation
use crate::tarantool::error::TarantoolErrorCode;
use crate::tarantool::set_error;
use crate::tarantool::tuple::{FunctionCtx, Tuple};
use crate::transport::rpc::{Request, Response};
use crate::transport::Context;
use std::fmt::{Debug, Display};
use std::io;
use std::ops::{Add, Deref};
use std::os::raw::c_int;
use std::rc::Rc;

pub trait RPCRoute {
    fn path(&self) -> &str;
    fn handle(&self, ctx: FunctionCtx, args: Tuple) -> c_int;
}

type HandlerFn<E> = dyn Fn(&mut Context, Request) -> Result<Response, E> + 'static;

pub struct Handler<E>(pub Box<HandlerFn<E>>);

impl<R, E, F> From<F> for Handler<E>
where
    R: Into<Response>,
    F: Fn(&mut Context, Request) -> Result<R, E> + 'static,
{
    fn from(f: F) -> Self {
        Handler(Box::new(move |ctx, req| f(ctx, req).map(Into::into)))
    }
}

impl<E> Deref for Handler<E> {
    type Target = Box<HandlerFn<E>>;

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

type MiddlewareFn<E> = dyn Fn(Handler<E>) -> Handler<E> + 'static;

pub struct Middleware<E>(pub Box<MiddlewareFn<E>>);

impl<E, F> From<F> for Middleware<E>
where
    F: Fn(Handler<E>) -> Handler<E> + 'static,
{
    fn from(f: F) -> Self {
        Middleware(Box::new(f))
    }
}

impl<E> Deref for Middleware<E> {
    type Target = Box<MiddlewareFn<E>>;

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

#[derive(thiserror::Error, Debug)]
pub enum Error<E: Display> {
    #[error("Parse request: {0}")]
    Parse(#[from] crate::tarantool::error::Error),
    #[error("{0}")]
    Handler(E),
}

pub type ErrorHandler<E> = fn(ctx: &Context, e: Error<E>);

pub struct Builder<E: Display> {
    path: String,
    middlewares: Vec<Rc<Middleware<E>>>,
    error_handler: Option<ErrorHandler<E>>,
}

impl<E: Display> Default for Builder<E> {
    fn default() -> Self {
        Builder::new()
    }
}

impl<E: Display> Builder<E> {
    pub fn new() -> Self {
        Self {
            path: "".to_string(),
            middlewares: vec![],
            error_handler: None,
        }
    }

    pub fn with_middleware(self, md: impl Into<Middleware<E>>) -> Self {
        let mut mw = self.middlewares;
        mw.push(Rc::new(md.into()));

        Self {
            middlewares: mw,
            ..self
        }
    }

    pub fn with_path(self, path: &'static str) -> Self {
        Self {
            path: self.path.add(path),
            ..self
        }
    }

    pub fn with_error_handler(self, h: ErrorHandler<E>) -> Self {
        Self {
            error_handler: Some(h),
            ..self
        }
    }

    pub fn group(self) -> Group<E> {
        Group { inner: self }
    }

    pub fn build(self, handler: impl Into<Handler<E>>) -> Route<E> {
        let handler = self
            .middlewares
            .into_iter()
            .fold(handler.into(), |handler, mw| (mw)(handler));

        let eh = self.error_handler.unwrap_or(|ctx: &Context, e: Error<E>| {
            match e {
                Error::Parse(_) => {
                    set_error!(TarantoolErrorCode::ProcC, "Invalid request format {}", e)
                }
                Error::Handler(_) => set_error!(
                    TarantoolErrorCode::ProcC,
                    "[{}]: {}",
                    ctx.get("request_id").unwrap_or("unknown"),
                    e
                ),
            };
        });

        Route {
            path: self.path,
            handler,
            error_handler: eh,
        }
    }
}

pub struct Group<E: Display> {
    inner: Builder<E>,
}

impl<E: Display> Group<E> {
    pub fn builder(&self) -> Builder<E> {
        Builder {
            path: self.inner.path.clone(),
            middlewares: self.inner.middlewares.clone(),
            error_handler: self.inner.error_handler,
        }
    }
}

pub struct Route<E: Display> {
    path: String,
    handler: Handler<E>,
    error_handler: ErrorHandler<E>,
}

impl<E> RPCRoute for Route<E>
where
    E: Display,
{
    fn path(&self) -> &str {
        &self.path
    }

    fn handle(&self, ctx: FunctionCtx, tup: Tuple) -> c_int {
        let mb_context = tup.try_get(1).and_then(|ctx| {
            ctx.ok_or_else(|| {
                crate::tarantool::error::Error::IO(io::Error::other("invalid tuple format"))
            })
        });
        let mut req_ctx: Context = match mb_context {
            Ok(ctx) => ctx,
            Err(e) => {
                (self.error_handler)(&Context::new(), Error::Parse(e));
                return -1;
            }
        };
        req_ctx.put("path", self.path.to_string());

        let result: Result<Response, E> = (self.handler)(&mut req_ctx, Request { tup });
        match result {
            Ok(resp) => ctx.return_bytes(&resp.bytes).unwrap(),
            Err(e) => {
                (self.error_handler)(&req_ctx, Error::Handler(e));
                -1
            }
        }
    }
}