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
}
}
}
}