use anyhow::Error;
pub fn codes(error: &Error) -> impl Iterator<Item = String> + '_ {
error.chain().filter_map(|cause| {
cause
.to_string()
.strip_prefix("error ")
.and_then(|rest| rest.split_whitespace().next())
.map(str::to_string)
})
}
pub fn code(error: &Error) -> Option<String> {
codes(error).next()
}
pub fn has_code(error: &Error, expected: &str) -> bool {
codes(error).any(|code| code == expected)
}
pub fn is_stale(error: &Error) -> bool {
error.is::<crate::sync::seed_claim::membership::StaleContext>()
}
pub fn is_access_refusal(error: &Error) -> bool {
has_code(error, "sync-server-refused")
|| has_code(error, "enrollment-unauthorized")
|| has_code(error, "enrollment-revoked")
|| has_code(error, "sync-device-removed")
}
pub fn is_vault_deleted(error: &Error) -> bool {
has_code(error, VAULT_DELETED)
}
const VAULT_DELETED: &str = "sync-vault-deleted";
pub(crate) fn hosting_error(hosting: aven_protocol::refusal::Hosting) -> Error {
use aven_protocol::refusal::Hosting;
let code = match hosting {
Hosting::Blocked => "sync-hosting-blocked",
Hosting::Deleted => VAULT_DELETED,
Hosting::Quota => "sync-hosting-quota",
Hosting::Unavailable => "sync-hosting-unavailable",
};
anyhow::anyhow!("error {code} outcome-unknown")
}
pub(crate) fn is_hosting_refusal(error: &Error) -> bool {
codes(error).any(|code| {
matches!(
code.as_str(),
"sync-hosting-blocked"
| "sync-hosting-quota"
| "sync-hosting-unavailable"
| VAULT_DELETED
)
})
}
#[cfg(test)]
mod tests {
use super::*;
use aven_protocol::refusal::Hosting;
#[test]
fn every_hosting_refusal_stops_transfers_and_only_deletion_is_terminal() {
for hosting in [
Hosting::Blocked,
Hosting::Deleted,
Hosting::Quota,
Hosting::Unavailable,
] {
let error = hosting_error(hosting).context("error sync-round");
assert!(is_hosting_refusal(&error));
assert!(!is_access_refusal(&error));
assert_eq!(is_vault_deleted(&error), hosting == Hosting::Deleted);
}
}
}