Skip to main content

auths_sdk/domains/credentials/
error.rs

1use auths_core::error::AuthsErrorInfo;
2use thiserror::Error;
3
4/// Errors from credential issuance, revocation, listing, and verification.
5///
6/// A credential is an ACDC anchored to the issuer's KEL via a backerless TEL
7/// (`vcp`/`iss`/`rev`). These errors mirror the SDK delegation surfaces:
8/// `thiserror` only, no `anyhow`, with an [`AuthsErrorInfo`] code + suggestion.
9///
10/// Usage:
11/// ```ignore
12/// match credentials::issue(&ctx, &issuer, issuee, &caps, role, None) {
13///     Err(CredentialError::IssueeNotFound { did }) => { /* issuee has no KEL */ }
14///     Err(e) => return Err(e.into()),
15///     Ok(result) => { /* credential SAID */ }
16/// }
17/// ```
18#[derive(Debug, Error)]
19#[non_exhaustive]
20pub enum CredentialError {
21    /// The issuee (subject/holder) has no KEL — an issuee must be incepted before a
22    /// credential can be issued against it (hard fail, never lazily created).
23    #[error("issuee identity not found (no KEL): {did}")]
24    IssueeNotFound {
25        /// The issuee `did:keri:` that has no resolvable KEL.
26        did: String,
27    },
28
29    /// The lazy registry (`vcp`) inception or a TEL anchor (`iss`/`rev`) failed.
30    #[error("credential registry error: {0}")]
31    RegistryError(#[source] auths_id::keri::credential_registry::CredentialRegistryError),
32
33    /// The credential is already revoked — revocation is idempotent, so this is only
34    /// surfaced when a caller asks to distinguish "already revoked" from a fresh `rev`.
35    #[error("credential already revoked: {said}")]
36    AlreadyRevoked {
37        /// The credential SAID that was already revoked.
38        said: String,
39    },
40
41    /// The issuer's KEL is `kt≥2` — single-signature credential anchoring only.
42    #[error("issuer is multi-signature (kt≥2); credential anchoring is single-author only")]
43    KtThresholdUnsupported,
44
45    /// The pinned capability schema SAID could not be computed (build-time invariant).
46    #[error("capability schema unknown or uncomputable")]
47    SchemaUnknown,
48
49    /// Verification could not reach a fresh-enough witnessed tip to judge the
50    /// credential's status — fail-closed (the resolution layer, F.4, owns this).
51    #[error("credential status is stale or unresolvable: {reason}")]
52    StaleOrUnresolvable {
53        /// Why no fresh witnessed tip was reachable.
54        reason: String,
55    },
56
57    /// A cryptographic operation failed (issuer-sign, curve resolution).
58    #[error("crypto error: {0}")]
59    CryptoError(#[source] auths_core::AgentError),
60}
61
62impl From<auths_id::keri::credential_registry::CredentialRegistryError> for CredentialError {
63    fn from(err: auths_id::keri::credential_registry::CredentialRegistryError) -> Self {
64        use auths_id::keri::credential_registry::CredentialRegistryError as RegErr;
65        match err {
66            RegErr::ThresholdUnsupported { .. } => CredentialError::KtThresholdUnsupported,
67            other => CredentialError::RegistryError(other),
68        }
69    }
70}
71
72impl From<auths_core::AgentError> for CredentialError {
73    fn from(err: auths_core::AgentError) -> Self {
74        CredentialError::CryptoError(err)
75    }
76}
77
78impl AuthsErrorInfo for CredentialError {
79    fn error_code(&self) -> &'static str {
80        match self {
81            Self::IssueeNotFound { .. } => "AUTHS-E6101",
82            Self::RegistryError(_) => "AUTHS-E6102",
83            Self::AlreadyRevoked { .. } => "AUTHS-E6103",
84            Self::KtThresholdUnsupported => "AUTHS-E6104",
85            Self::SchemaUnknown => "AUTHS-E6105",
86            Self::StaleOrUnresolvable { .. } => "AUTHS-E6106",
87            Self::CryptoError(e) => e.error_code(),
88        }
89    }
90
91    fn suggestion(&self) -> Option<&'static str> {
92        match self {
93            Self::IssueeNotFound { .. } => Some(
94                "The issuee must have an incepted identity (KEL) before it can be credentialed",
95            ),
96            Self::RegistryError(_) => {
97                Some("Check the issuer identity and registry storage are reachable")
98            }
99            Self::AlreadyRevoked { .. } => {
100                Some("This credential is already revoked; no further action is needed")
101            }
102            Self::KtThresholdUnsupported => {
103                Some("Credential issuance currently requires a single-signature (kt=1) issuer")
104            }
105            Self::SchemaUnknown => {
106                Some("The compiled-in capability schema is unavailable; this is a build defect")
107            }
108            Self::StaleOrUnresolvable { .. } => Some(
109                "No fresh witnessed tip was reachable; sync the issuer KEL/receipts and retry, or relax --require-witnesses",
110            ),
111            Self::CryptoError(e) => e.suggestion(),
112        }
113    }
114}