use crate::runtime::EngineContext;
use super::{CapabilityError, CapabilityInvocation, CapabilityOutcome, CapabilityRegistry};
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum CapabilityDispatchResult {
Outcome(CapabilityOutcome),
Error(CapabilityError),
}
impl CapabilityDispatchResult {
pub fn is_ok(&self) -> bool {
matches!(self, CapabilityDispatchResult::Outcome(_))
}
pub fn is_err(&self) -> bool {
!self.is_ok()
}
pub fn as_outcome(&self) -> Option<&CapabilityOutcome> {
match self {
CapabilityDispatchResult::Outcome(o) => Some(o),
CapabilityDispatchResult::Error(_) => None,
}
}
pub fn as_error(&self) -> Option<&CapabilityError> {
match self {
CapabilityDispatchResult::Outcome(_) => None,
CapabilityDispatchResult::Error(e) => Some(e),
}
}
pub fn into_outcome(self) -> Option<CapabilityOutcome> {
match self {
CapabilityDispatchResult::Outcome(o) => Some(o),
CapabilityDispatchResult::Error(_) => None,
}
}
pub fn into_error(self) -> Option<CapabilityError> {
match self {
CapabilityDispatchResult::Outcome(_) => None,
CapabilityDispatchResult::Error(e) => Some(e),
}
}
}
pub fn dispatch(
registry: &CapabilityRegistry,
context: &EngineContext,
invocation: &CapabilityInvocation,
) -> CapabilityDispatchResult {
if context.cancellation().is_cancelled() {
return CapabilityDispatchResult::Error(CapabilityError::Cancelled);
}
if context.is_expired() {
return CapabilityDispatchResult::Error(CapabilityError::DeadlineExpired);
}
let entry = match registry.get(invocation.capability_id()) {
Some(entry) => entry,
None => return CapabilityDispatchResult::Error(CapabilityError::Unknown),
};
match entry.handler().invoke(context, invocation) {
Ok(outcome) => CapabilityDispatchResult::Outcome(outcome),
Err(error) => CapabilityDispatchResult::Error(error),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::capability::{
CapabilityDefinition, CapabilityError, CapabilityHandler, CapabilityInvocation,
CapabilityOutcome, arc_handler,
};
use crate::identity::{CapabilityId, ContractId, EngineId};
use crate::identity::{CorrelationId, OperationId};
use crate::operation::{Operation, OperationContext};
use crate::runtime::Deadline;
use std::sync::Arc;
use std::time::Duration;
fn make_context() -> EngineContext {
let operation = Operation::new(
OperationId::new("op-1").unwrap(),
CorrelationId::new("corr-1").unwrap(),
);
EngineContext::new(OperationContext::new(operation))
}
fn make_invocation(cap_id: CapabilityId) -> CapabilityInvocation {
CapabilityInvocation::new(
cap_id,
ContractId::new("test.request").unwrap(),
b"test payload".to_vec(),
)
}
fn make_handler() -> Arc<dyn CapabilityHandler> {
arc_handler(|_ctx: &EngineContext, _invocation: &CapabilityInvocation| {
Ok(CapabilityOutcome::new(b"handler response".to_vec()))
})
}
fn register_handler(registry: &CapabilityRegistry, cap_id: CapabilityId) {
let def = CapabilityDefinition::new(
cap_id.clone(),
EngineId::new("test.engine").unwrap(),
"Test Handler",
)
.unwrap();
registry.register(def, make_handler()).unwrap();
}
#[test]
fn dispatch_invokes_registered_handler() {
let registry = CapabilityRegistry::new();
let cap_id = CapabilityId::new("test.cap").unwrap();
register_handler(®istry, cap_id.clone());
let context = make_context();
let invocation = make_invocation(cap_id);
let result = dispatch(®istry, &context, &invocation);
assert!(result.is_ok());
let outcome = result.into_outcome().unwrap();
let bytes = outcome.into_bytes();
assert_eq!(bytes, b"handler response");
}
#[test]
fn dispatch_passes_the_request_context_to_handler() {
let registry = CapabilityRegistry::new();
let cap_id = CapabilityId::new("context.cap").unwrap();
let observed_operation_id = Arc::new(std::sync::Mutex::new(None));
let observed_operation_id_by_handler = Arc::clone(&observed_operation_id);
let handler: Arc<dyn CapabilityHandler> =
arc_handler(move |context: &EngineContext, _: &CapabilityInvocation| {
*observed_operation_id_by_handler.lock().unwrap() =
Some(context.operation().operation.id.clone());
Ok(CapabilityOutcome::new(b"context received".to_vec()))
});
let definition = CapabilityDefinition::new(
cap_id.clone(),
EngineId::new("test.engine").unwrap(),
"Context Handler",
)
.unwrap();
registry.register(definition, handler).unwrap();
let context = make_context();
let expected_operation_id = context.operation().operation.id.clone();
let invocation = make_invocation(cap_id);
let result = dispatch(®istry, &context, &invocation);
assert!(result.is_ok());
assert_eq!(
*observed_operation_id.lock().unwrap(),
Some(expected_operation_id)
);
}
#[test]
fn dispatch_returns_unknown_for_missing_capability() {
let registry = CapabilityRegistry::new();
let context = make_context();
let invocation = make_invocation(CapabilityId::new("missing").unwrap());
let result = dispatch(®istry, &context, &invocation);
assert!(result.is_err());
assert!(matches!(result.as_error(), Some(CapabilityError::Unknown)));
}
#[test]
fn dispatch_returns_cancelled_error_for_cancelled_context() {
let registry = CapabilityRegistry::new();
let cap_id = CapabilityId::new("test.cap").unwrap();
register_handler(®istry, cap_id.clone());
let context = make_context();
context.cancellation().cancel();
let invocation = make_invocation(cap_id);
let result = dispatch(®istry, &context, &invocation);
assert!(matches!(
result.as_error(),
Some(CapabilityError::Cancelled)
));
}
#[test]
fn dispatch_returns_expired_error_for_expired_context() {
let registry = CapabilityRegistry::new();
let cap_id = CapabilityId::new("test.cap").unwrap();
register_handler(®istry, cap_id.clone());
let context = make_context().with_deadline(Deadline::from_now(Duration::ZERO).unwrap());
let invocation = make_invocation(cap_id);
let result = dispatch(®istry, &context, &invocation);
assert!(matches!(
result.as_error(),
Some(CapabilityError::DeadlineExpired)
));
}
#[test]
fn dispatch_propagates_handler_errors() {
let failing_handler: Arc<dyn CapabilityHandler> =
arc_handler(|_: &EngineContext, _: &CapabilityInvocation| {
Err(CapabilityError::HandlerFailed("handler crashed".into()))
});
let registry = CapabilityRegistry::new();
let def = CapabilityDefinition::new(
CapabilityId::new("failing.cap").unwrap(),
EngineId::new("test.engine").unwrap(),
"Failing Handler",
)
.unwrap();
registry.register(def, failing_handler).unwrap();
let context = make_context();
let invocation = make_invocation(CapabilityId::new("failing.cap").unwrap());
let result = dispatch(®istry, &context, &invocation);
assert!(result.is_err());
assert!(matches!(
result.as_error(),
Some(CapabilityError::HandlerFailed(_))
));
}
#[test]
fn dispatch_checks_cancellation_before_missing_capability() {
let registry = CapabilityRegistry::new();
let context = make_context();
context.cancellation().cancel();
let invocation = make_invocation(CapabilityId::new("missing.cap").unwrap());
let result = dispatch(®istry, &context, &invocation);
assert!(result.is_err());
assert!(matches!(
result.as_error(),
Some(CapabilityError::Cancelled)
));
}
#[test]
fn dispatch_checks_deadline_before_missing_capability() {
let registry = CapabilityRegistry::new();
let context = make_context().with_deadline(Deadline::from_now(Duration::ZERO).unwrap());
let invocation = make_invocation(CapabilityId::new("missing.cap").unwrap());
let result = dispatch(®istry, &context, &invocation);
assert!(result.is_err());
assert!(matches!(
result.as_error(),
Some(CapabilityError::DeadlineExpired)
));
}
#[test]
fn dispatch_result_outcome_accessors() {
let outcome = CapabilityOutcome::new(b"accessor test".to_vec());
let result = CapabilityDispatchResult::Outcome(outcome);
assert!(result.is_ok());
assert!(!result.is_err());
assert!(result.as_outcome().is_some());
assert!(result.as_error().is_none());
let extracted = result.into_outcome();
assert!(extracted.is_some());
assert_eq!(extracted.unwrap().into_bytes(), b"accessor test");
}
#[test]
fn dispatch_result_error_accessors() {
let result = CapabilityDispatchResult::Error(CapabilityError::Unknown);
assert!(!result.is_ok());
assert!(result.is_err());
assert!(result.as_outcome().is_none());
assert!(result.as_error().is_some());
let extracted = result.into_error();
assert!(extracted.is_some());
assert!(matches!(extracted.unwrap(), CapabilityError::Unknown));
}
#[test]
fn dispatch_result_debug() {
let ok_result = CapabilityDispatchResult::Outcome(CapabilityOutcome::new(b"ok".to_vec()));
let err_result = CapabilityDispatchResult::Error(CapabilityError::Unknown);
assert!(format!("{:?}", ok_result).contains("Outcome"));
assert!(format!("{:?}", err_result).contains("Error"));
}
}