osslsigncode 0.1.1

In-process Rust bindings for the vendored osslsigncode Authenticode implementation
//! Errors returned by the typed Authenticode API.
//!
//! ```
//! use osslsigncode::{Error, Unsigned};
//!
//! let err = Unsigned::open("/no/such/input.bin").unwrap_err();
//! assert!(matches!(err, Error::Io { .. }));
//! assert!(err.status_code().is_none());
//! ```

use std::ffi::NulError;
use std::io;
use std::path::PathBuf;

use thiserror::Error;

/// Convenient alias for this crate's error type.
pub type Result<T> = std::result::Result<T, Error>;

/// Errors returned by the safe Rust wrapper.
#[derive(Debug, Error)]
pub enum Error {
    /// A path or secret cannot be represented as a C string.
    #[error("{field} contains an interior NUL byte")]
    InvalidCString {
        /// Which structured field failed to convert.
        field: &'static str,
        #[source]
        source: NulError,
    },
    /// A required input could not be read before entering the native library.
    #[error("{field}: {source}: {}", path.display())]
    Io {
        /// Structured field that pointed at the path.
        field: &'static str,
        /// Path that failed.
        path: PathBuf,
        #[source]
        source: io::Error,
    },
    /// The Rust adapter rejected the job before entering the native body.
    #[error("osslsigncode runtime error: {message}")]
    Runtime {
        /// Human-readable adapter error.
        message: String,
    },
    /// The native implementation returned a non-zero status.
    #[error("{operation} failed with status {code}{detail}")]
    Failed {
        /// Job that was invoked.
        operation: &'static str,
        /// Status returned by the upstream implementation.
        code: i32,
        /// Native error text, including a leading `: ` when present.
        detail: String,
    },
}

impl Error {
    /// Raw status when the native library ran and failed.
    #[must_use]
    pub fn status_code(&self) -> Option<i32> {
        match self {
            Self::Failed { code, .. } => Some(*code),
            _ => None,
        }
    }
}

pub(crate) fn failed(operation: &'static str, code: i32, message: Option<String>) -> Error {
    let detail = message
        .filter(|text| !text.is_empty())
        .map(|text| format!(": {text}"))
        .unwrap_or_default();
    Error::Failed {
        operation,
        code,
        detail,
    }
}

pub(crate) fn io(field: &'static str, path: impl Into<PathBuf>, source: io::Error) -> Error {
    Error::Io {
        field,
        path: path.into(),
        source,
    }
}