use cruxi::{
CodedError, Context, FailValidator, Handler, HandlerFn, PassValidator, ServiceFn,
ValidatingHandler, ValidatingHandlerError, Validator, ValidatorFn,
};
#[test]
fn handler_fn_returns_error() {
let handler: HandlerFn<_, _, CodedError> =
HandlerFn::new(|_ctx: &Context, req: i32| -> Result<i32, CodedError> {
if req < 0 {
Err(CodedError::new("NEGATIVE_VALUE"))
} else {
Ok(req * 2)
}
});
let result = handler.handle(&Context::new(), -5);
assert!(result.is_err());
assert_eq!(result.unwrap_err().code(), "NEGATIVE_VALUE");
}
#[test]
fn handler_fn_uses_context() {
#[derive(Clone)]
struct UserId(u64);
let handler: HandlerFn<_, _, CodedError> =
HandlerFn::new(|ctx: &Context, _req: ()| -> Result<u64, CodedError> {
ctx.get::<UserId>()
.map(|u| u.0)
.ok_or_else(|| CodedError::new("NO_USER_ID"))
});
let ctx = Context::new().with_value(UserId(42));
let result = handler.handle(&ctx, ());
assert_eq!(result.ok(), Some(42));
}
#[test]
fn handler_fn_context_deadline_check() {
use std::time::Duration;
let handler: HandlerFn<_, _, CodedError> =
HandlerFn::new(|ctx: &Context, _req: ()| -> Result<bool, CodedError> { Ok(ctx.is_done()) });
let ctx = Context::with_timeout(Duration::from_secs(60));
assert_eq!(handler.handle(&ctx, ()).ok(), Some(false));
let ctx = Context::with_timeout(Duration::from_nanos(1));
std::thread::sleep(Duration::from_millis(1));
assert_eq!(handler.handle(&ctx, ()).ok(), Some(true));
}
#[test]
fn validating_handler_passes_request_through() {
let validator =
ValidatorFn::new(|_ctx: &Context, _req: &String| -> Result<(), CodedError> { Ok(()) });
let service = ServiceFn::new(|_ctx: &Context, req: String| -> Result<usize, CodedError> {
Ok(req.len())
});
let handler = ValidatingHandler::new(validator, service);
let result = handler.handle(&Context::new(), "hello world".to_string());
assert_eq!(result.ok(), Some(11));
}
#[test]
fn validating_handler_validation_error_stops_service() {
let call_count = std::cell::RefCell::new(0);
let validator = ValidatorFn::new(|_ctx: &Context, _req: &i32| -> Result<(), CodedError> {
Err(CodedError::new("ALWAYS_FAIL"))
});
let service = ServiceFn::new(|_ctx: &Context, _req: i32| -> Result<i32, CodedError> {
*call_count.borrow_mut() += 1;
Ok(42)
});
let handler = ValidatingHandler::new(validator, service);
let result = handler.handle(&Context::new(), 123);
assert!(result.is_err());
assert_eq!(*call_count.borrow(), 0); }
#[test]
fn validating_handler_error_is_validation_error() {
let validator = ValidatorFn::new(|_ctx: &Context, _req: &i32| -> Result<(), CodedError> {
Err(CodedError::new("VALIDATION_FAILED"))
});
let service = ServiceFn::new(|_ctx: &Context, req: i32| -> Result<i32, CodedError> { Ok(req) });
let handler = ValidatingHandler::new(validator, service);
let result = handler.handle(&Context::new(), 42);
match result {
Err(ValidatingHandlerError::Validation(e)) => {
assert_eq!(e.code(), "VALIDATION_FAILED");
}
Err(ValidatingHandlerError::Service(e)) => {
panic!("Expected validation error, got service error: {e}");
}
Ok(value) => panic!("Expected validation error, got success value: {value}"),
}
}
#[test]
fn validating_handler_error_is_service_error() {
let validator =
ValidatorFn::new(|_ctx: &Context, _req: &i32| -> Result<(), CodedError> { Ok(()) });
let service = ServiceFn::new(|_ctx: &Context, _req: i32| -> Result<i32, CodedError> {
Err(CodedError::new("SERVICE_FAILED"))
});
let handler = ValidatingHandler::new(validator, service);
let result = handler.handle(&Context::new(), 42);
match result {
Err(ValidatingHandlerError::Service(e)) => {
assert_eq!(e.code(), "SERVICE_FAILED");
}
Err(ValidatingHandlerError::Validation(e)) => {
panic!("Expected service error, got validation error: {e}");
}
Ok(value) => panic!("Expected service error, got success value: {value}"),
}
}
#[test]
fn validator_fn_multiple_conditions() {
#[derive(Clone)]
struct UserInput {
username: String,
age: u8,
}
let validator = ValidatorFn::new(
|_ctx: &Context, input: &UserInput| -> Result<(), CodedError> {
if input.username.is_empty() {
return Err(CodedError::new("USERNAME_REQUIRED"));
}
if input.username.len() < 3 {
return Err(CodedError::new("USERNAME_TOO_SHORT"));
}
if input.age < 18 {
return Err(CodedError::new("AGE_REQUIREMENT"));
}
Ok(())
},
);
let valid = UserInput {
username: "johndoe".to_string(),
age: 25,
};
assert!(validator.validate(&Context::new(), &valid).is_ok());
let empty_name = UserInput {
username: "".to_string(),
age: 25,
};
assert_eq!(
validator
.validate(&Context::new(), &empty_name)
.err()
.map(|e| e.code().to_string()),
Some("USERNAME_REQUIRED".to_string())
);
let short_name = UserInput {
username: "ab".to_string(),
age: 25,
};
assert_eq!(
validator
.validate(&Context::new(), &short_name)
.err()
.map(|e| e.code().to_string()),
Some("USERNAME_TOO_SHORT".to_string())
);
let underage = UserInput {
username: "johndoe".to_string(),
age: 16,
};
assert_eq!(
validator
.validate(&Context::new(), &underage)
.err()
.map(|e| e.code().to_string()),
Some("AGE_REQUIREMENT".to_string())
);
}
#[test]
fn pass_validator_always_passes() {
let validator = PassValidator;
assert!(validator.validate(&Context::new(), &42i32).is_ok());
assert!(validator.validate(&Context::new(), &"any string").is_ok());
assert!(validator.validate(&Context::new(), &vec![1, 2, 3]).is_ok());
}
#[test]
fn fail_validator_always_fails() {
let validator = FailValidator::new(CodedError::new("CIRCUIT_BREAKER"));
let result = validator.validate(&Context::new(), &42i32);
assert!(result.is_err());
assert_eq!(result.unwrap_err().code(), "CIRCUIT_BREAKER");
}
#[test]
fn fail_validator_preserves_error_details() {
let validator = FailValidator::new(
CodedError::new("MAINTENANCE")
.with_title("System Maintenance")
.with_reason("The system is currently under maintenance"),
);
let result = validator.validate(&Context::new(), &"any");
let err = result.unwrap_err();
assert_eq!(err.code(), "MAINTENANCE");
assert_eq!(err.title(), Some("System Maintenance"));
assert_eq!(
err.reason(),
Some("The system is currently under maintenance")
);
}
#[test]
fn validator_uses_context() {
#[derive(Clone)]
struct RequesterId;
let validator = ValidatorFn::new(|ctx: &Context, _req: &i32| -> Result<(), CodedError> {
match ctx.get::<RequesterId>() {
Some(_) => Ok(()),
None => Err(CodedError::new("NO_REQUESTER")),
}
});
let result = validator.validate(&Context::new(), &42);
assert!(result.is_err());
let ctx = Context::new().with_value(RequesterId);
let result = validator.validate(&ctx, &42);
assert!(result.is_ok());
}
#[test]
fn validating_handler_error_display_validation() {
let err: ValidatingHandlerError<CodedError, CodedError> =
ValidatingHandlerError::Validation(CodedError::new("VAL_ERROR").with_reason("bad input"));
let display = err.to_string();
assert!(display.contains("validation error"));
assert!(display.contains("bad input"));
}
#[test]
fn validating_handler_error_display_service() {
let err: ValidatingHandlerError<CodedError, CodedError> =
ValidatingHandlerError::Service(CodedError::new("SVC_ERROR").with_reason("service failed"));
let display = err.to_string();
assert!(display.contains("service error"));
assert!(display.contains("service failed"));
}
#[test]
fn validating_handler_error_source() {
use std::error::Error;
let inner = CodedError::new("INNER");
let err: ValidatingHandlerError<CodedError, CodedError> =
ValidatingHandlerError::Validation(inner);
assert!(err.source().is_some());
}