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>,
message: String,
},
}
#[non_exhaustive]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CandidCallContext {
pub operation: &'static str,
pub canister_id: Principal,
pub caller: Principal,
pub method: String,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CanisterInstallPhase {
CreateCanister,
AddCycles,
InstallCode,
}
impl CanisterInstallPhase {
#[must_use]
pub const fn operation(self) -> &'static str {
match self {
Self::CreateCanister => "create_canister",
Self::AddCycles => "add_cycles",
Self::InstallCode => "install_canister",
}
}
}
#[derive(Debug, Eq, PartialEq)]
pub struct CanisterInstallError {
phase: CanisterInstallPhase,
canister_id: Option<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, message } => {
write!(f, "pocket_ic {} failed: {message}", context.operation)?;
context
}
};
write!(
f,
" (operation={}, canister={}, caller={}, method={})",
context.operation, context.canister_id, context.caller, context.method
)
}
}
impl std::error::Error for CandidCallError {}
impl CanisterInstallError {
#[must_use]
pub const fn new(
phase: CanisterInstallPhase,
canister_id: Option<Principal>,
label: Option<String>,
source: PocketIcOperationError,
) -> Self {
Self {
phase,
canister_id,
label,
source,
}
}
#[must_use]
pub const fn phase(&self) -> CanisterInstallPhase {
self.phase
}
#[must_use]
pub const fn canister_id(&self) -> Option<Principal> {
self.canister_id
}
#[must_use]
pub fn message(&self) -> &str {
self.source.message()
}
#[must_use]
pub const fn operation_error(&self) -> &PocketIcOperationError {
&self.source
}
#[must_use]
pub fn label(&self) -> Option<&str> {
self.label.as_deref()
}
}
impl std::fmt::Display for CanisterInstallError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} failed", self.phase.operation())?;
if let Some(canister_id) = self.canister_id {
write!(f, " for canister {canister_id}")?;
}
if let Some(label) = &self.label {
write!(f, " ({label})")?;
}
write!(f, ": {}", self.source)
}
}
impl std::error::Error for CanisterInstallError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.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::new(
super::CanisterInstallPhase::InstallCode,
Some(Principal::anonymous()),
Some("authority".into()),
super::PocketIcOperationError::new("trap"),
);
assert_eq!(err.label(), Some("authority"));
assert!(err.to_string().contains("(authority): trap"));
}
}