bark-apns 0.1.0

Direct APNs client for sending Bark notifications.
Documentation
/// Result type used by this crate.
///
/// The default error type is [`Error`], but the alias accepts a custom error
/// parameter when needed.
pub type Result<T, E = Error> = std::result::Result<T, E>;

/// Error type for Bark APNs operations.
///
/// The enum is non-exhaustive so the crate can add more precise variants later
/// without breaking downstream code. Applications can convert it into
/// `anyhow::Error` with `?`, while libraries can match individual variants.
#[non_exhaustive]
#[derive(Debug, thiserror::Error)]
pub enum Error {
    /// The encryption key length does not match the selected AES algorithm.
    #[error(
        "invalid encryption key length for {algorithm}: expected {expected} bytes, got {actual}"
    )]
    InvalidKeyLength {
        /// Bark algorithm name, such as `AES128`.
        algorithm: &'static str,
        /// Expected UTF-8 byte length.
        expected: usize,
        /// Actual UTF-8 byte length.
        actual: usize,
    },

    /// The IV length does not match the selected AES mode.
    #[error("invalid IV length for {mode}: expected {expected} bytes, got {actual}")]
    InvalidIvLength {
        /// Bark mode name, such as `CBC`.
        mode: &'static str,
        /// Expected UTF-8 byte length.
        expected: usize,
        /// Actual UTF-8 byte length.
        actual: usize,
    },

    /// The message id is too long to be used as an APNs collapse id.
    #[error("invalid APNs collapse id: expected at most 64 bytes, got {actual}")]
    InvalidCollapseId {
        /// Actual UTF-8 byte length.
        actual: usize,
    },

    /// A delete message was serialized or sent without an id.
    #[error("delete messages require an id")]
    MissingMessageIdForDelete,

    /// A normal non-delete message has neither body nor markdown.
    #[error("message body or markdown is required")]
    EmptyMessage,

    /// A value could not be encoded as an APNs HTTP header.
    #[error("invalid APNs header value for {name}: {source}")]
    InvalidHeaderValue {
        /// Header name being constructed.
        name: &'static str,
        /// Underlying header parsing error.
        #[source]
        source: reqwest::header::InvalidHeaderValue,
    },

    /// The HTTP client could not be constructed.
    #[error("failed to build HTTP client: {0}")]
    HttpClient(#[source] reqwest::Error),

    /// An HTTP request failed before receiving a per-device APNs response.
    #[error("APNs request failed: {0}")]
    Http(#[from] reqwest::Error),

    /// APNs provider-token generation failed.
    #[error("APNs token generation failed: {0}")]
    Jwt(#[from] jsonwebtoken::errors::Error),

    /// JSON serialization failed.
    #[error("failed to serialize payload: {0}")]
    Json(#[from] serde_json::Error),

    /// Encryption failed after input validation.
    #[error("encryption failed")]
    Encryption,

    /// Random IV generation failed.
    #[error("failed to generate random IV: {0}")]
    Random(
        /// Randomness provider error text.
        String,
    ),

    /// One or more device tokens failed during a multi-device send.
    #[error("APNs failed for one or more devices: {0}")]
    ApnsFailures(
        /// Human-readable summary containing failed device tokens and reasons.
        String,
    ),
}

impl From<getrandom::Error> for Error {
    fn from(error: getrandom::Error) -> Self {
        Self::Random(error.to_string())
    }
}