use crate::Context;
use crate::error::CruxiError;
use crate::service::Service;
use crate::validator::Validator;
pub trait Handler<Req, Resp> {
type Error: std::error::Error;
fn handle(&self, ctx: &Context, req: Req) -> Result<Resp, Self::Error>;
}
#[derive(Clone)]
pub struct HandlerFn<F, Resp, E>
where
E: std::error::Error,
{
f: F,
_marker: std::marker::PhantomData<(Resp, E)>,
}
impl<F, Resp, E> HandlerFn<F, Resp, E>
where
E: std::error::Error,
{
pub fn new<Req>(f: F) -> Self
where
F: Fn(&Context, Req) -> Result<Resp, E>,
{
Self {
f,
_marker: std::marker::PhantomData,
}
}
}
impl<F, Req, Resp, E> Handler<Req, Resp> for HandlerFn<F, Resp, E>
where
F: Fn(&Context, Req) -> Result<Resp, E>,
E: std::error::Error,
{
type Error = E;
fn handle(&self, ctx: &Context, req: Req) -> Result<Resp, Self::Error> {
(self.f)(ctx, req)
}
}
#[derive(Debug)]
pub enum ValidatingHandlerError<VE, SE> {
Validation(VE),
Service(SE),
NilService,
}
impl<VE, SE> std::fmt::Display for ValidatingHandlerError<VE, SE>
where
VE: std::fmt::Display,
SE: std::fmt::Display,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Validation(e) => write!(f, "validation error: {e}"),
Self::Service(e) => write!(f, "service error: {e}"),
Self::NilService => {
let err = CruxiError::NilService;
write!(f, "{err}")
}
}
}
}
impl<VE, SE> std::error::Error for ValidatingHandlerError<VE, SE>
where
VE: std::error::Error + 'static,
SE: std::error::Error + 'static,
{
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Validation(e) => Some(e),
Self::Service(e) => Some(e),
Self::NilService => None,
}
}
}
pub struct ValidatingHandler<V, S, Req, Resp>
where
V: Validator<Req>,
S: Service<Req, Resp>,
{
pub validator: Option<V>,
pub service: Option<S>,
_marker: std::marker::PhantomData<(Req, Resp)>,
}
impl<V, S, Req, Resp> ValidatingHandler<V, S, Req, Resp>
where
V: Validator<Req>,
S: Service<Req, Resp>,
{
pub fn new(validator: Option<V>, service: Option<S>) -> Self {
Self {
validator,
service,
_marker: std::marker::PhantomData,
}
}
}
impl<V, S, Req, Resp> Handler<Req, Resp> for ValidatingHandler<V, S, Req, Resp>
where
V: Validator<Req>,
V::Error: 'static,
S: Service<Req, Resp>,
S::Error: 'static,
Req: Clone,
{
type Error = ValidatingHandlerError<V::Error, S::Error>;
fn handle(&self, ctx: &Context, req: Req) -> Result<Resp, Self::Error> {
let Some(service) = &self.service else {
return Err(ValidatingHandlerError::NilService);
};
if let Some(validator) = &self.validator {
validator
.validate(ctx, &req)
.map_err(ValidatingHandlerError::Validation)?;
}
service
.execute(ctx, req)
.map_err(ValidatingHandlerError::Service)
}
}
#[cfg(feature = "async")]
use async_trait::async_trait;
#[cfg(feature = "async")]
#[async_trait]
pub trait AsyncHandler<Req, Resp>: Send + Sync
where
Req: Send,
Resp: Send,
{
type Error: std::error::Error + Send;
async fn handle(&self, ctx: &Context, req: Req) -> Result<Resp, Self::Error>;
}
#[cfg(feature = "async")]
#[async_trait]
impl<H, Req, Resp> AsyncHandler<Req, Resp> for H
where
H: Handler<Req, Resp> + Send + Sync,
H::Error: Send,
Req: Send + 'static,
Resp: Send,
{
type Error = H::Error;
async fn handle(&self, ctx: &Context, req: Req) -> Result<Resp, Self::Error> {
Handler::handle(self, ctx, req)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{CodedError, ServiceFn, ValidatorFn};
#[test]
fn handler_fn_basic() {
let handler =
HandlerFn::new(|_ctx: &Context, req: i32| -> Result<i32, CodedError> { Ok(req * 2) });
let result = Handler::handle(&handler, &Context::new(), 21);
assert_eq!(result.ok(), Some(42));
}
#[test]
fn validating_handler_with_valid_request() {
let validator = ValidatorFn::new(|_ctx: &Context, req: &i32| -> Result<(), CodedError> {
if *req > 0 {
Ok(())
} else {
Err(CodedError::new("INVALID"))
}
});
let service =
ServiceFn::new(|_ctx: &Context, req: i32| -> Result<i32, CodedError> { Ok(req * 2) });
let handler = ValidatingHandler::new(Some(validator), Some(service));
let result = Handler::handle(&handler, &Context::new(), 21);
assert_eq!(result.ok(), Some(42));
}
#[test]
fn validating_handler_with_invalid_request() {
let validator = ValidatorFn::new(|_ctx: &Context, req: &i32| -> Result<(), CodedError> {
if *req > 0 {
Ok(())
} else {
Err(CodedError::new("INVALID"))
}
});
let service =
ServiceFn::new(|_ctx: &Context, req: i32| -> Result<i32, CodedError> { Ok(req * 2) });
let handler = ValidatingHandler::new(Some(validator), Some(service));
let result = Handler::handle(&handler, &Context::new(), -1);
assert!(result.is_err());
}
#[test]
fn validating_handler_nil_service() {
let validator =
ValidatorFn::new(|_ctx: &Context, _req: &i32| -> Result<(), CodedError> { Ok(()) });
type DummyServiceFn = fn(&Context, i32) -> Result<i32, CodedError>;
let handler: ValidatingHandler<_, ServiceFn<DummyServiceFn, i32, CodedError>, i32, i32> =
ValidatingHandler::new(Some(validator), None);
let result = Handler::handle(&handler, &Context::new(), 42);
match result {
Err(ValidatingHandlerError::NilService) => (),
_ => panic!("Expected NilService error"),
}
}
#[test]
fn validating_handler_no_validator() {
let service =
ServiceFn::new(|_ctx: &Context, req: i32| -> Result<i32, CodedError> { Ok(req * 2) });
type DummyValidatorFn = fn(&Context, &i32) -> Result<(), CodedError>;
let handler: ValidatingHandler<ValidatorFn<DummyValidatorFn, CodedError>, _, _, _> =
ValidatingHandler::new(None, Some(service));
let result = Handler::handle(&handler, &Context::new(), 21);
assert_eq!(result.ok(), Some(42));
}
}