use core::fmt;
use std::collections::BTreeMap;
use chrono::{DateTime, Utc};
use crate::did::{OneOrMany, Uri};
use crate::jose::{JwsError, VerificationError};
use crate::resolver::{KeyReference, KeyReferenceError, KeyResolutionError};
use crate::vc::CredentialError;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ValidationError {
#[error("invalid JWS: {0}")]
Jws(#[from] JwsError),
#[error(transparent)]
KeyReference(#[from] KeyReferenceError),
#[error("header `typ` is {found:?}, not `{expected}`")]
UnexpectedTyp {
expected: &'static str,
found: String,
},
#[error("the payload is not a JWT claims set: {0}")]
Payload(String),
#[error(transparent)]
Credential(#[from] CredentialError),
#[error("the payload carries `{claim}`, so it is an SD-JWT, not a plain JWT")]
SdJwtPayload {
claim: &'static str,
},
#[error("resolving key {key}: {source}")]
KeyResolution {
key: Box<KeyReference>,
#[source]
source: KeyResolutionError,
},
#[error("signature: {0}")]
Signature(#[from] VerificationError),
#[error("issuer `{issuer}` does not control signing key {key}")]
IssuerMismatch {
issuer: Uri,
key: Box<KeyReference>,
},
#[error("{role} `{id}` is not a DID")]
NotDid {
role: &'static str,
id: String,
},
#[error("holder `{holder}` does not control signing key {key}")]
HolderMismatch {
holder: Uri,
key: Box<KeyReference>,
},
#[error("not valid before {} (now is {})", not_before.to_rfc3339(), now.to_rfc3339())]
NotYetValid {
not_before: DateTime<Utc>,
now: DateTime<Utc>,
},
#[error("expired at {} (now is {})", expired_at.to_rfc3339(), now.to_rfc3339())]
Expired {
expired_at: DateTime<Utc>,
now: DateTime<Utc>,
},
#[error("issued at {}, after now ({})", issued_at.to_rfc3339(), now.to_rfc3339())]
IssuedInFuture {
issued_at: DateTime<Utc>,
now: DateTime<Utc>,
},
#[error("audience {found:?} does not include {expected:?}")]
AudienceMismatch {
expected: String,
found: Option<OneOrMany<String>>,
},
#[error("nonce {found:?} is not {expected:?}")]
NonceMismatch {
expected: String,
found: Option<String>,
},
#[error("`credentialStatus` of type `{0}` cannot be checked")]
StatusUnchecked(String),
#[error("credential status: {0}")]
Status(#[from] StatusError),
#[error("credential subject {subject:?} is not the holder `{holder}`")]
SubjectHolderMismatch {
subject: Option<Uri>,
holder: Uri,
},
#[cfg(feature = "sd-jwt")]
#[cfg_attr(docsrs, doc(cfg(feature = "sd-jwt")))]
#[error(transparent)]
SdJwt(Box<crate::sd_jwt_vc::SdJwtValidationError>),
}
#[cfg(feature = "sd-jwt")]
impl From<crate::sd_jwt_vc::SdJwtValidationError> for ValidationError {
fn from(error: crate::sd_jwt_vc::SdJwtValidationError) -> Self {
match error {
crate::sd_jwt_vc::SdJwtValidationError::Validation(error) => error,
other => Self::SdJwt(Box::new(other)),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum StatusError {
#[error("status type `{0}` is not supported")]
Unsupported(String),
#[error("status unavailable: {0}")]
Unavailable(String),
#[error("credential is {0}")]
Invalid(String),
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub struct PresentationValidationError {
pub presentation: Option<Box<ValidationError>>,
pub credentials: BTreeMap<usize, ValidationError>,
}
impl PresentationValidationError {
pub fn presentation(error: ValidationError) -> Self {
Self {
presentation: Some(Box::new(error)),
credentials: BTreeMap::new(),
}
}
pub fn credentials(errors: BTreeMap<usize, ValidationError>) -> Self {
Self {
presentation: None,
credentials: errors,
}
}
}
impl fmt::Display for PresentationValidationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(error) = &self.presentation {
return write!(f, "presentation: {error}");
}
for (i, (index, error)) in self.credentials.iter().enumerate() {
if i > 0 {
f.write_str("; ")?;
}
write!(f, "credential {index}: {error}")?;
}
Ok(())
}
}