use candid::Principal;
use pocket_ic::{PocketIc, RejectResponse};
use super::{PocketIcInstance, PocketIcOperationError};
#[non_exhaustive]
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum CandidCallError {
Encode {
context: Box<CandidCallContext>,
message: String,
},
Decode {
context: Box<CandidCallContext>,
bytes: usize,
message: String,
},
CanisterReject {
context: Box<CandidCallContext>,
response: Box<RejectResponse>,
},
Transport {
context: Box<CandidCallContext>,
source: PocketIcOperationError,
},
}
#[non_exhaustive]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CandidCallContext {
pub operation: &'static str,
pub canister_id: Principal,
pub caller: Principal,
pub method: String,
}
#[non_exhaustive]
#[derive(Debug, Eq, PartialEq)]
pub enum CanisterInstallError {
CreateCanister {
label: Option<String>,
source: PocketIcOperationError,
},
AddCycles {
canister_id: Principal,
label: Option<String>,
source: PocketIcOperationError,
},
InstallCode {
canister_id: Principal,
label: Option<String>,
source: PocketIcOperationError,
},
}
pub struct StandaloneCanisterInstallError {
instance: Box<PocketIcInstance<PocketIc>>,
install_error: CanisterInstallError,
}
impl CandidCallContext {
#[must_use]
pub fn new(
operation: &'static str,
canister_id: Principal,
caller: Principal,
method: impl Into<String>,
) -> Self {
Self {
operation,
canister_id,
caller,
method: method.into(),
}
}
}
impl std::fmt::Display for CandidCallError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let context = match self {
Self::Encode { context, message } => {
write!(f, "candid encode_args failed: {message}")?;
context
}
Self::Decode {
context,
bytes,
message,
} => {
write!(f, "candid decode failed (bytes={bytes}): {message}")?;
context
}
Self::CanisterReject { context, response } => {
write!(
f,
"pocket_ic {} was rejected: {response}",
context.operation
)?;
context
}
Self::Transport { context, source } => {
write!(f, "pocket_ic {} failed: {source}", context.operation)?;
context
}
};
write!(
f,
" (operation={}, canister={}, caller={}, method={})",
context.operation, context.canister_id, context.caller, context.method
)
}
}
impl std::error::Error for CandidCallError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Transport { source, .. } => Some(source),
_ => None,
}
}
}
impl std::fmt::Display for CanisterInstallError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let (operation, canister_id, label, source) = match self {
Self::CreateCanister { label, source } => ("create_canister", None, label, source),
Self::AddCycles {
canister_id,
label,
source,
} => ("add_cycles", Some(canister_id), label, source),
Self::InstallCode {
canister_id,
label,
source,
} => ("install_canister", Some(canister_id), label, source),
};
write!(f, "{operation} failed")?;
if let Some(canister_id) = canister_id {
write!(f, " for canister {canister_id}")?;
}
if let Some(label) = label {
write!(f, " ({label})")?;
}
write!(f, ": {source}")
}
}
impl std::error::Error for CanisterInstallError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
let (Self::CreateCanister { source, .. }
| Self::AddCycles { source, .. }
| Self::InstallCode { source, .. }) = self;
Some(source)
}
}
impl StandaloneCanisterInstallError {
pub(super) fn new(
instance: PocketIcInstance<PocketIc>,
install_error: CanisterInstallError,
) -> Self {
Self {
instance: Box::new(instance),
install_error,
}
}
#[must_use]
pub fn pocket_ic(&self) -> &PocketIc {
&self.instance.instance
}
#[must_use]
pub const fn install_error(&self) -> &CanisterInstallError {
&self.install_error
}
pub fn into_parts(self) -> (PocketIcInstance<PocketIc>, CanisterInstallError) {
(*self.instance, self.install_error)
}
}
impl std::fmt::Debug for StandaloneCanisterInstallError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("StandaloneCanisterInstallError")
.field("install_error", &self.install_error)
.finish_non_exhaustive()
}
}
impl std::fmt::Display for StandaloneCanisterInstallError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.install_error.fmt(f)
}
}
impl std::error::Error for StandaloneCanisterInstallError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.install_error)
}
}
#[cfg(test)]
mod tests {
use candid::Principal;
use super::CanisterInstallError;
#[test]
fn labeled_install_error_display_includes_label() {
let err = CanisterInstallError::InstallCode {
canister_id: Principal::anonymous(),
label: Some("authority".into()),
source: super::PocketIcOperationError::new("trap"),
};
assert!(err.to_string().contains("(authority): trap"));
}
}