Skip to main content

canic_core/api/
error.rs

1use crate::{InternalError, InternalErrorClass, InternalErrorOrigin, dto::error::Error};
2
3fn internal_error_to_public(err: &InternalError) -> Error {
4    if let Some(public) = err.public_error() {
5        return public.clone();
6    }
7
8    let message = err.to_string();
9
10    match err.class() {
11        InternalErrorClass::Access => Error::unauthorized(message),
12
13        InternalErrorClass::Domain => match err.origin() {
14            InternalErrorOrigin::Config => Error::invalid(message),
15            _ => Error::conflict(message),
16        },
17
18        InternalErrorClass::Invariant => Error::invariant(message),
19
20        InternalErrorClass::Infra | InternalErrorClass::Ops | InternalErrorClass::Workflow => {
21            Error::internal(message)
22        }
23    }
24}
25
26impl From<&InternalError> for Error {
27    fn from(err: &InternalError) -> Self {
28        internal_error_to_public(err)
29    }
30}
31
32impl From<InternalError> for Error {
33    fn from(err: InternalError) -> Self {
34        internal_error_to_public(&err)
35    }
36}
37
38// -----------------------------------------------------------------------------
39// Tests
40// -----------------------------------------------------------------------------
41
42#[cfg(test)]
43mod tests {
44    use super::*;
45    use crate::{
46        access::AccessError,
47        domain::policy::pure::{
48            component_allocation::ComponentAllocationPolicyError,
49            component_child_allocation::ComponentChildAllocationPolicyError,
50        },
51        dto::error::ErrorCode,
52        ids::CanisterRole,
53    };
54
55    #[test]
56    fn internal_error_mapping_matches_class_contract() {
57        let access: Error = InternalError::from(AccessError::Denied("denied".to_string())).into();
58        assert_eq!(access.code, ErrorCode::Unauthorized);
59
60        let domain_config: Error =
61            InternalError::domain(InternalErrorOrigin::Config, "bad config").into();
62        assert_eq!(domain_config.code, ErrorCode::InvalidInput);
63
64        let domain_other: Error =
65            InternalError::domain(InternalErrorOrigin::Domain, "conflict").into();
66        assert_eq!(domain_other.code, ErrorCode::Conflict);
67
68        let invariant: Error =
69            InternalError::invariant(InternalErrorOrigin::Ops, "broken invariant").into();
70        assert_eq!(invariant.code, ErrorCode::InvariantViolation);
71
72        let infra: Error = InternalError::infra(InternalErrorOrigin::Infra, "infra fail").into();
73        assert_eq!(infra.code, ErrorCode::Internal);
74
75        let ops: Error = InternalError::ops(InternalErrorOrigin::Ops, "ops fail").into();
76        assert_eq!(ops.code, ErrorCode::Internal);
77
78        let workflow: Error =
79            InternalError::workflow(InternalErrorOrigin::Workflow, "workflow fail").into();
80        assert_eq!(workflow.code, ErrorCode::Internal);
81
82        let invalid_allocation: Error =
83            InternalError::from(ComponentAllocationPolicyError::EmptyOperationId).into();
84        assert_eq!(invalid_allocation.code, ErrorCode::InvalidInput);
85
86        let exhausted_allocation: Error =
87            InternalError::from(ComponentAllocationPolicyError::ComponentCapacityExhausted).into();
88        assert_eq!(exhausted_allocation.code, ErrorCode::ResourceExhausted);
89
90        let invalid_authority: Error =
91            InternalError::from(ComponentAllocationPolicyError::RootTopologyDigestMismatch).into();
92        assert_eq!(invalid_authority.code, ErrorCode::InvariantViolation);
93
94        let forbidden_child: Error =
95            InternalError::from(ComponentChildAllocationPolicyError::SpawnGrantMissing {
96                parent_role: CanisterRole::new("project_hub"),
97                child_role: CanisterRole::new("project_ledger"),
98            })
99            .into();
100        assert_eq!(forbidden_child.code, ErrorCode::Forbidden);
101
102        let stale_child: Error = InternalError::from(
103            ComponentChildAllocationPolicyError::ComponentRegistryAuthorityMismatch,
104        )
105        .into();
106        assert_eq!(stale_child.code, ErrorCode::Conflict);
107
108        let exhausted_child: Error = InternalError::from(
109            ComponentChildAllocationPolicyError::ComponentDescendantCapacityExhausted,
110        )
111        .into();
112        assert_eq!(exhausted_child.code, ErrorCode::ResourceExhausted);
113
114        let token_expired: Error = AccessError::DelegatedAuthTokenExpired.into();
115        assert_eq!(token_expired.code, ErrorCode::AuthTokenExpired);
116
117        let cert_expired: Error = AccessError::DelegatedAuthCertExpired.into();
118        assert_eq!(cert_expired.code, ErrorCode::AuthProofExpired);
119    }
120
121    #[test]
122    fn public_error_is_preserved_without_remap() {
123        let public = Error::not_found("missing");
124        let remapped: Error = InternalError::public(public.clone()).into();
125        assert_eq!(remapped, public);
126    }
127}