//! Typed errors for SD-JWT VC issuance, presentation, and verification.

use thiserror::Error;

/// A failure handling an SD-JWT VC or its keys.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum Error {
    /// A value was not valid base64url.
    #[error("invalid base64url in {context}")]
    Base64 {
        /// Where the bad value appeared (a JWS segment, a disclosure, …).
        context: &'static str,
        /// The decode failure.
        #[source]
        source: base64::DecodeError,
    },

    /// A JSON value could not be encoded or decoded.
    #[error("invalid JSON in {context}")]
    Json {
        /// Where the bad JSON appeared.
        context: &'static str,
        /// The serde failure.
        #[source]
        source: serde_json::Error,
    },

    /// A compact JWS did not have exactly three dot-separated segments.
    #[error("malformed compact JWS: expected 3 segments, found {found}")]
    MalformedJws {
        /// How many segments were present.
        found: usize,
    },

    /// A JWK did not describe a supported key (`kty=EC`, `crv=P-256`).
    #[error("unsupported JWK: {reason}")]
    UnsupportedJwk {
        /// Why the key was rejected.
        reason: String,
    },

    /// An EC point or scalar was the wrong length or off the curve.
    #[error("invalid EC key material: {reason}")]
    InvalidKeyMaterial {
        /// Why the material was rejected.
        reason: String,
    },

    /// The operating system CSPRNG could not supply a salt.
    #[error("could not gather entropy for a disclosure salt")]
    Entropy,

    /// Producing an ECDSA signature failed.
    #[error("could not sign")]
    Signing,

    /// An ECDSA signature did not verify against the expected key.
    #[error("signature verification failed for {context}")]
    BadSignature {
        /// Which token failed (issuer JWS or key-binding JWT).
        context: &'static str,
    },

    /// A PKCS#8 encode/decode of a private key failed.
    #[error("invalid PKCS#8 private key")]
    Pkcs8,

    /// An SD-JWT was missing a structural element.
    #[error("malformed SD-JWT: {reason}")]
    MalformedSdJwt {
        /// What was missing or malformed.
        reason: String,
    },

    /// A presented disclosure's digest was not among the credential's `_sd`
    /// digests, so the issuer never committed to that claim.
    #[error("disclosure {name:?} is not committed by the credential")]
    UncommittedDisclosure {
        /// The claim name carried by the offending disclosure.
        name: String,
    },

    /// The credential is outside its validity window.
    #[error("credential not valid at this time ({reason})")]
    NotValidNow {
        /// Whether it was not yet valid or already expired.
        reason: &'static str,
    },

    /// The key-binding proof did not match what the verifier required.
    #[error("key-binding check failed: {reason}")]
    KeyBinding {
        /// Which binding check failed (audience, nonce, sd_hash, type).
        reason: &'static str,
    },
}

impl Error {
    pub(crate) fn json(context: &'static str) -> impl FnOnce(serde_json::Error) -> Self {
        move |source| Self::Json { context, source }
    }

    pub(crate) fn base64(context: &'static str) -> impl FnOnce(base64::DecodeError) -> Self {
        move |source| Self::Base64 { context, source }
    }
}
