chemx-ext 0.3.0

External-program interfaces (xtb, CREST) and the RDKit-free fallback conformer generator for chemx.
Documentation
//! Error type shared by the external-program interfaces.

use std::path::PathBuf;

/// Errors from the external-program (xtb / CREST) interfaces and the
/// fallback conformer generator.
#[derive(Debug, thiserror::Error)]
pub enum ExtError {
    /// The external binary was not found on `PATH` or via its override
    /// environment variable. Carries install guidance.
    #[error(
        "{program} binary not found: not on PATH and {env_var} is not set. \
         Install it from {install_hint} (conda: `conda install -c conda-forge {conda_pkg}`), \
         or point {env_var} at the executable."
    )]
    BinaryNotFound {
        /// Program name (`xtb` or `crest`).
        program: &'static str,
        /// Override environment variable (`CHEMX_XTB_PATH` / `CHEMX_CREST_PATH`).
        env_var: &'static str,
        /// Where to obtain the program.
        install_hint: &'static str,
        /// conda-forge package name.
        conda_pkg: &'static str,
    },

    /// The subprocess ran but exited with a failure status (or aborted).
    #[error("{program} failed (exit status {status}): {stderr_tail}")]
    SubprocessFailed {
        program: &'static str,
        status: String,
        /// Last portion of stderr/stdout for diagnosis.
        stderr_tail: String,
    },

    /// An expected output file was not produced.
    #[error("{program} did not produce the expected output file {path}")]
    MissingOutput {
        program: &'static str,
        path: PathBuf,
    },

    /// An output file existed but could not be parsed.
    #[error("parsing {what}: {message}")]
    Parse {
        /// Which output (e.g. "TURBOMOLE gradient file").
        what: &'static str,
        message: String,
    },

    /// Filesystem / process-spawn errors.
    #[error("I/O error ({context}): {source}")]
    Io {
        context: String,
        #[source]
        source: std::io::Error,
    },

    /// Conformer-generator errors (no rotatable bonds is *not* an error; this
    /// covers structural problems such as ghost atoms or an empty molecule).
    #[error("conformer generation: {0}")]
    ConfGen(String),
}

impl ExtError {
    /// Convenience constructor for [`ExtError::Io`].
    pub fn io(context: impl Into<String>, source: std::io::Error) -> Self {
        ExtError::Io {
            context: context.into(),
            source,
        }
    }
}