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#[cfg(test)]
43mod tests {
44 use super::*;
45 use crate::{
46 access::AccessError,
47 cdk::types::Principal,
48 domain::policy::pure::{
49 component_allocation::ComponentAllocationPolicyError,
50 topology::{TopologyPolicyError, registry::RegistryPolicyError},
51 },
52 dto::error::ErrorCode,
53 ids::CanisterRole,
54 };
55
56 fn p(id: u8) -> Principal {
57 Principal::from_slice(&[id; 29])
58 }
59
60 #[test]
61 fn internal_error_mapping_matches_class_contract() {
62 let access: Error = InternalError::from(AccessError::Denied("denied".to_string())).into();
63 assert_eq!(access.code, ErrorCode::Unauthorized);
64
65 let domain_config: Error =
66 InternalError::domain(InternalErrorOrigin::Config, "bad config").into();
67 assert_eq!(domain_config.code, ErrorCode::InvalidInput);
68
69 let domain_other: Error =
70 InternalError::domain(InternalErrorOrigin::Domain, "conflict").into();
71 assert_eq!(domain_other.code, ErrorCode::Conflict);
72
73 let invariant: Error =
74 InternalError::invariant(InternalErrorOrigin::Ops, "broken invariant").into();
75 assert_eq!(invariant.code, ErrorCode::InvariantViolation);
76
77 let infra: Error = InternalError::infra(InternalErrorOrigin::Infra, "infra fail").into();
78 assert_eq!(infra.code, ErrorCode::Internal);
79
80 let ops: Error = InternalError::ops(InternalErrorOrigin::Ops, "ops fail").into();
81 assert_eq!(ops.code, ErrorCode::Internal);
82
83 let workflow: Error =
84 InternalError::workflow(InternalErrorOrigin::Workflow, "workflow fail").into();
85 assert_eq!(workflow.code, ErrorCode::Internal);
86
87 let invalid_allocation: Error =
88 InternalError::from(ComponentAllocationPolicyError::EmptyOperationId).into();
89 assert_eq!(invalid_allocation.code, ErrorCode::InvalidInput);
90
91 let exhausted_allocation: Error =
92 InternalError::from(ComponentAllocationPolicyError::ComponentCapacityExhausted).into();
93 assert_eq!(exhausted_allocation.code, ErrorCode::ResourceExhausted);
94
95 let invalid_authority: Error =
96 InternalError::from(ComponentAllocationPolicyError::RootTopologyDigestMismatch).into();
97 assert_eq!(invalid_authority.code, ErrorCode::InvariantViolation);
98
99 let token_expired: Error = AccessError::DelegatedAuthTokenExpired.into();
100 assert_eq!(token_expired.code, ErrorCode::AuthTokenExpired);
101
102 let cert_expired: Error = AccessError::DelegatedAuthCertExpired.into();
103 assert_eq!(cert_expired.code, ErrorCode::AuthProofExpired);
104 }
105
106 #[test]
107 fn public_error_is_preserved_without_remap() {
108 let public = Error::not_found("missing");
109 let remapped: Error = InternalError::public(public.clone()).into();
110 assert_eq!(remapped, public);
111 }
112
113 #[test]
114 fn registry_policy_errors_map_to_stable_public_policy_codes() {
115 let err = RegistryPolicyError::RoleAlreadyRegistered {
116 role: CanisterRole::new("app"),
117 pid: p(7),
118 };
119 let internal: InternalError = TopologyPolicyError::from(err).into();
120 let public: Error = internal.into();
121 assert_eq!(public.code, ErrorCode::PolicyRoleAlreadyRegistered);
122 }
123
124 #[test]
125 fn registry_policy_service_parent_errors_use_service_owned_public_codes() {
126 let cases = [
127 (
128 RegistryPolicyError::ReplicaRequiresServiceWithScaling {
129 role: CanisterRole::new("replica"),
130 parent_role: CanisterRole::new("plain_parent"),
131 },
132 ErrorCode::PolicyReplicaRequiresServiceWithScaling,
133 ),
134 (
135 RegistryPolicyError::ShardRequiresServiceWithSharding {
136 role: CanisterRole::new("shard"),
137 parent_role: CanisterRole::new("plain_parent"),
138 },
139 ErrorCode::PolicyShardRequiresServiceWithSharding,
140 ),
141 (
142 RegistryPolicyError::InstanceRequiresServiceWithIndex {
143 role: CanisterRole::new("instance"),
144 parent_role: CanisterRole::new("plain_parent"),
145 },
146 ErrorCode::PolicyInstanceRequiresServiceWithIndex,
147 ),
148 ];
149
150 for (err, expected) in cases {
151 let internal: InternalError = TopologyPolicyError::from(err).into();
152 let public: Error = internal.into();
153
154 assert_eq!(public.code, expected);
155 }
156 }
157}