use crate::{InternalError, InternalErrorClass, InternalErrorOrigin, dto::error::Error};
fn internal_error_to_public(err: &InternalError) -> Error {
if let Some(public) = err.public_error() {
return public.clone();
}
let message = err.to_string();
match err.class() {
InternalErrorClass::Access => Error::unauthorized(message),
InternalErrorClass::Domain => match err.origin() {
InternalErrorOrigin::Config => Error::invalid(message),
_ => Error::conflict(message),
},
InternalErrorClass::Invariant => Error::invariant(message),
InternalErrorClass::Infra | InternalErrorClass::Ops | InternalErrorClass::Workflow => {
Error::internal(message)
}
}
}
impl From<&InternalError> for Error {
fn from(err: &InternalError) -> Self {
internal_error_to_public(err)
}
}
impl From<InternalError> for Error {
fn from(err: InternalError) -> Self {
internal_error_to_public(&err)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
access::AccessError,
domain::policy::pure::{
component_allocation::ComponentAllocationPolicyError,
component_child_allocation::ComponentChildAllocationPolicyError,
},
dto::error::ErrorCode,
ids::CanisterRole,
};
#[test]
fn internal_error_mapping_matches_class_contract() {
let access: Error = InternalError::from(AccessError::Denied("denied".to_string())).into();
assert_eq!(access.code, ErrorCode::Unauthorized);
let domain_config: Error =
InternalError::domain(InternalErrorOrigin::Config, "bad config").into();
assert_eq!(domain_config.code, ErrorCode::InvalidInput);
let domain_other: Error =
InternalError::domain(InternalErrorOrigin::Domain, "conflict").into();
assert_eq!(domain_other.code, ErrorCode::Conflict);
let invariant: Error =
InternalError::invariant(InternalErrorOrigin::Ops, "broken invariant").into();
assert_eq!(invariant.code, ErrorCode::InvariantViolation);
let infra: Error = InternalError::infra(InternalErrorOrigin::Infra, "infra fail").into();
assert_eq!(infra.code, ErrorCode::Internal);
let ops: Error = InternalError::ops(InternalErrorOrigin::Ops, "ops fail").into();
assert_eq!(ops.code, ErrorCode::Internal);
let workflow: Error =
InternalError::workflow(InternalErrorOrigin::Workflow, "workflow fail").into();
assert_eq!(workflow.code, ErrorCode::Internal);
let invalid_allocation: Error =
InternalError::from(ComponentAllocationPolicyError::EmptyOperationId).into();
assert_eq!(invalid_allocation.code, ErrorCode::InvalidInput);
let exhausted_allocation: Error =
InternalError::from(ComponentAllocationPolicyError::ComponentCapacityExhausted).into();
assert_eq!(exhausted_allocation.code, ErrorCode::ResourceExhausted);
let invalid_authority: Error =
InternalError::from(ComponentAllocationPolicyError::RootTopologyDigestMismatch).into();
assert_eq!(invalid_authority.code, ErrorCode::InvariantViolation);
let forbidden_child: Error =
InternalError::from(ComponentChildAllocationPolicyError::SpawnGrantMissing {
parent_role: CanisterRole::new("project_hub"),
child_role: CanisterRole::new("project_ledger"),
})
.into();
assert_eq!(forbidden_child.code, ErrorCode::Forbidden);
let stale_child: Error = InternalError::from(
ComponentChildAllocationPolicyError::ComponentRegistryAuthorityMismatch,
)
.into();
assert_eq!(stale_child.code, ErrorCode::Conflict);
let exhausted_child: Error = InternalError::from(
ComponentChildAllocationPolicyError::ComponentDescendantCapacityExhausted,
)
.into();
assert_eq!(exhausted_child.code, ErrorCode::ResourceExhausted);
let token_expired: Error = AccessError::DelegatedAuthTokenExpired.into();
assert_eq!(token_expired.code, ErrorCode::AuthTokenExpired);
let cert_expired: Error = AccessError::DelegatedAuthCertExpired.into();
assert_eq!(cert_expired.code, ErrorCode::AuthProofExpired);
}
#[test]
fn public_error_is_preserved_without_remap() {
let public = Error::not_found("missing");
let remapped: Error = InternalError::public(public.clone()).into();
assert_eq!(remapped, public);
}
}