use crate::{
access::AccessError,
diagnostics::{RegisteredDiagnosticCode, codes},
domain::policy::pure::{
component_allocation::ComponentAllocationPolicyError,
component_child_allocation::ComponentChildAllocationPolicyError,
},
dto::error::Error as PublicError,
};
use std::fmt;
#[derive(Debug)]
pub struct InternalError {
code: RegisteredDiagnosticCode,
projection: PublicProjection,
platform_rejection: Option<String>,
provisioning_failure: Option<Box<crate::view::provisioning_failure::ProvisioningFailureView>>,
}
#[derive(Clone, Copy, Debug)]
enum PublicProjection {
Registered(RegisteredDiagnosticCode),
Forwarded(PublicError),
}
impl InternalError {
pub const MAX_PLATFORM_REJECTION_BYTES: usize = 1024;
const fn new(code: RegisteredDiagnosticCode, public_code: RegisteredDiagnosticCode) -> Self {
Self {
code,
projection: PublicProjection::Registered(public_code),
provisioning_failure: None,
platform_rejection: None,
}
}
#[must_use]
pub const fn public(code: RegisteredDiagnosticCode) -> Self {
Self::projected(code, code)
}
#[must_use]
pub const fn projected(
code: RegisteredDiagnosticCode,
public_code: RegisteredDiagnosticCode,
) -> Self {
Self {
code,
projection: PublicProjection::Registered(public_code),
provisioning_failure: None,
platform_rejection: None,
}
}
#[must_use]
pub const fn observed_public(err: PublicError) -> Self {
Self {
code: codes::PLATFORM_FAILED,
projection: PublicProjection::Forwarded(err),
provisioning_failure: None,
platform_rejection: None,
}
}
#[must_use]
pub fn with_platform_rejection(mut self, message: &str) -> Self {
let mut end = message.len().min(Self::MAX_PLATFORM_REJECTION_BYTES);
while !message.is_char_boundary(end) {
end -= 1;
}
self.platform_rejection = Some(message[..end].to_owned());
self
}
#[must_use]
pub fn platform_rejection(&self) -> Option<&str> {
self.platform_rejection.as_deref()
}
#[must_use]
pub fn with_provisioning_failure(
mut self,
stage: crate::domain::provisioning_failure::ProvisioningFailureStage,
target: candid::Principal,
operation_id: [u8; 32],
retry_category: crate::domain::provisioning_failure::ProvisioningRetryCategory,
) -> Self {
if self.provisioning_failure.is_none() {
self.provisioning_failure = Some(Box::new(
crate::view::provisioning_failure::ProvisioningFailureView {
recorded_at_ns: None,
retry_at_ns: None,
stage,
target,
operation_id,
diagnostic_code: self.public_error().raw_code(),
retry_category,
},
));
}
self
}
#[must_use]
pub fn with_observed_provisioning_failure(
mut self,
origin: crate::view::provisioning_failure::ProvisioningFailureView,
) -> Self {
self.provisioning_failure
.get_or_insert_with(|| Box::new(origin));
self
}
#[must_use]
pub fn provisioning_failure(
&self,
) -> Option<crate::view::provisioning_failure::ProvisioningFailureView> {
self.provisioning_failure.as_deref().copied()
}
#[must_use]
pub const fn forbidden() -> Self {
Self::public(crate::diagnostics::codes::AUTHORITY_UNAUTHORIZED)
}
#[must_use]
pub const fn invalid_input() -> Self {
Self::public(crate::diagnostics::codes::REQUEST_INVALID)
}
#[must_use]
pub const fn conflict() -> Self {
Self::public(crate::diagnostics::codes::STATE_CONFLICT)
}
#[must_use]
pub const fn unavailable() -> Self {
Self::public(crate::diagnostics::codes::STATE_UNAVAILABLE)
}
#[must_use]
pub const fn resource_exhausted() -> Self {
Self::public(crate::diagnostics::codes::CAPACITY_LIMIT)
}
#[must_use]
pub const fn auth_material_stale() -> Self {
Self::public(codes::SECURITY_CONFLICT)
}
#[must_use]
pub const fn auth_proof_expired() -> Self {
Self::public(codes::AUTH_CERT_EXPIRED)
}
#[must_use]
pub const fn auth_token_expired() -> Self {
Self::public(crate::diagnostics::codes::AUTH_TOKEN_EXPIRED)
}
#[must_use]
pub const fn auth_proof_pending() -> Self {
Self::public(crate::diagnostics::codes::SECURITY_UNAVAILABLE)
}
#[must_use]
pub const fn operation_id_required() -> Self {
Self::public(crate::diagnostics::codes::AUTHORITY_UNAVAILABLE)
}
#[must_use]
pub const fn root_data_certificate_unavailable() -> Self {
Self::public(crate::diagnostics::codes::SECURITY_UNAVAILABLE)
}
#[must_use]
pub const fn invariant() -> Self {
Self::new(codes::STATE_INVALID, codes::STATE_INVALID)
}
#[must_use]
pub const fn platform_failure() -> Self {
Self::new(codes::PLATFORM_FAILED, codes::STATE_FAILED)
}
#[must_use]
pub const fn state_failure() -> Self {
Self::new(codes::STATE_FAILED, codes::STATE_FAILED)
}
#[must_use]
pub const fn lifecycle_failure() -> Self {
Self::new(codes::LIFECYCLE_FAILED, codes::STATE_FAILED)
}
#[must_use]
pub const fn code(&self) -> RegisteredDiagnosticCode {
self.code
}
#[must_use]
pub const fn public_code(&self) -> Option<RegisteredDiagnosticCode> {
match self.projection {
PublicProjection::Registered(code) => Some(code),
PublicProjection::Forwarded(_) => None,
}
}
#[must_use]
pub const fn public_error(&self) -> PublicError {
match self.projection {
PublicProjection::Registered(code) => PublicError::from_registered(code),
PublicProjection::Forwarded(error) => error,
}
}
#[must_use]
pub fn is_public_resource_exhausted(&self) -> bool {
self.public_error().code() == codes::CAPACITY_LIMIT.raw_code()
}
}
impl fmt::Display for InternalError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.code, f)
}
}
impl std::error::Error for InternalError {}
impl From<AccessError> for InternalError {
fn from(err: AccessError) -> Self {
match err {
AccessError::Internal(error) => error,
error => {
let diagnostic = error
.diagnostic_codes()
.expect("non-internal access errors have registered reasons");
Self::projected(diagnostic.exact, diagnostic.public)
}
}
}
}
impl From<ComponentAllocationPolicyError> for InternalError {
fn from(err: ComponentAllocationPolicyError) -> Self {
use crate::diagnostics::codes;
let code = match err {
ComponentAllocationPolicyError::EmptyOperationId => codes::REQUEST_INCOMPLETE,
ComponentAllocationPolicyError::AllocationSequenceExhausted => codes::VERSION_CAPACITY,
ComponentAllocationPolicyError::InvalidRootTopologyProjection => {
codes::COLLECTION_INVALID
}
ComponentAllocationPolicyError::RootTopologyDigestMismatch
| ComponentAllocationPolicyError::ComponentSpecHashMismatch(_) => {
codes::DIGEST_CONFLICT
}
ComponentAllocationPolicyError::ComponentSpecNotAdmitted(_) => {
codes::AUTHORITY_INVALID_STATE
}
ComponentAllocationPolicyError::ComponentSpecUnknown(_)
| ComponentAllocationPolicyError::PeerProvisioningGrantMissing { .. } => {
codes::CONFIGURATION_UNAVAILABLE
}
ComponentAllocationPolicyError::ComponentCountOverflow
| ComponentAllocationPolicyError::ComponentCapacityExhausted
| ComponentAllocationPolicyError::ComponentSpecCountOverflow(_)
| ComponentAllocationPolicyError::ComponentSpecCapacityExhausted(_)
| ComponentAllocationPolicyError::PeerProvisioningCountOverflow
| ComponentAllocationPolicyError::PeerProvisioningCapacityExhausted { .. } => {
codes::CAPACITY_LIMIT
}
ComponentAllocationPolicyError::InvalidPeerRequesterBinding => codes::AUTHORITY_INVALID,
ComponentAllocationPolicyError::PeerRootRuntimeInactive
| ComponentAllocationPolicyError::PeerRequesterRegistryMemberInactive => {
codes::AUTHORITY_INACTIVE
}
};
Self::public(code)
}
}
impl From<ComponentChildAllocationPolicyError> for InternalError {
fn from(err: ComponentChildAllocationPolicyError) -> Self {
use crate::diagnostics::codes;
let code = match err {
ComponentChildAllocationPolicyError::EmptyOperationId => codes::REQUEST_INCOMPLETE,
ComponentChildAllocationPolicyError::InvalidComponentBinding
| ComponentChildAllocationPolicyError::InvalidParentBinding => codes::AUTHORITY_INVALID,
ComponentChildAllocationPolicyError::ParentComponentMismatch
| ComponentChildAllocationPolicyError::ParentCallerMismatch
| ComponentChildAllocationPolicyError::ComponentRegistryAuthorityMismatch => {
codes::AUTHORITY_CONFLICT
}
ComponentChildAllocationPolicyError::FleetRegistryRootNotActive
| ComponentChildAllocationPolicyError::RootRuntimeNotActive
| ComponentChildAllocationPolicyError::ComponentRegistryNotActive
| ComponentChildAllocationPolicyError::ParentRegistryMemberNotActive => {
codes::AUTHORITY_INACTIVE
}
ComponentChildAllocationPolicyError::ComponentSpecUnknown(_)
| ComponentChildAllocationPolicyError::SpawnGrantMissing { .. } => {
codes::CONFIGURATION_UNAVAILABLE
}
ComponentChildAllocationPolicyError::ChildRoleNotAdmitted { .. } => {
codes::AUTHORITY_INVALID_STATE
}
ComponentChildAllocationPolicyError::ParentRoleCountOverflow
| ComponentChildAllocationPolicyError::ParentRoleCapacityExhausted { .. }
| ComponentChildAllocationPolicyError::InitialBootstrapUnavailable
| ComponentChildAllocationPolicyError::ComponentDescendantCapacityExhausted
| ComponentChildAllocationPolicyError::ComponentCountOverflow => codes::CAPACITY_LIMIT,
ComponentChildAllocationPolicyError::InvalidDeploymentLimits => {
codes::CONFIGURATION_INVALID
}
};
Self::public(code)
}
}