Skip to main content

pmpx_engine/
error.rs

1//! Why a command could not be started.
2//!
3//! Two outcomes, because the next step differs: "there is no such program" is a setup problem the
4//! user can fix (and the message lists the near misses), while "it is there and would not start" is
5//! something to report with its own cause. Neither is this crate's decision to print.
6
7use std::ffi::{OsStr, OsString};
8use std::fmt;
9
10/// A program that could not be started.
11#[derive(Debug)]
12pub enum EngineError {
13    /// The plugin store refused -- installing, removing, or looking something up -- and the kit's own
14    /// message is the whole explanation; this crate has nothing to add to it.
15    ///
16    /// Behind the `store` feature, because the kit is an optional dependency: a host that only runs
17    /// plugins does not pay for `ureq`, `rustls` and `ring`, and neither does its error type.
18    #[cfg(feature = "store")]
19    Kit(crate_plugin_kit::KitError),
20
21    /// The program was not found: either the path a plugin pointed at does not exist, or no name on
22    /// `PATH` matches.
23    ///
24    /// The message is built here rather than by the caller because only this crate knows what was
25    /// searched, and it is the part a person needs to see.
26    NotFound(String),
27
28    /// The user asked for something impossible (`-C` at a directory that is not there).
29    Usage(String),
30
31    /// The setup could not be completed: the store, the manifest list, the project root.
32    Setup(String),
33
34    /// No project type was detected, or the pinned plugin cannot answer. Exit code 3.
35    NoProject(String),
36
37    /// The decision refused to choose, with the reason the decision crate reported.
38    Detect(pmpx_detect::DetectFailure),
39
40    /// The plugin answered that it cannot do this.
41    Call {
42        /// The plugin's name.
43        plugin: String,
44        /// The verb it refused.
45        verb: String,
46        /// What it said.
47        error: pmpx_loader::CallError,
48    },
49
50    /// It was found, and starting it failed anyway.
51    Start {
52        /// The program, as it was about to be started.
53        program: OsString,
54        /// What the operating system said.
55        source: std::io::Error,
56    },
57}
58
59impl EngineError {
60    /// Refuse a program, with the message a person needs.
61    pub(crate) fn not_found(message: String) -> Self {
62        Self::NotFound(message)
63    }
64
65    /// The full explanation, for whoever is showing it.
66    pub fn message(&self) -> String {
67        match self {
68            #[cfg(feature = "store")]
69            Self::Kit(error) => error.to_string(),
70            Self::NotFound(message) | Self::Usage(message) | Self::Setup(message) => {
71                message.clone()
72            }
73            Self::NoProject(message) => message.clone(),
74            Self::Detect(failure) => failure.message(),
75            Self::Call {
76                plugin,
77                verb,
78                error,
79            } => format!("plugin {plugin} cannot do `{verb}`: {error}"),
80            Self::Start { program, source } => {
81                format!("failed to start {}: {source}", program.to_string_lossy())
82            }
83        }
84    }
85
86    /// The program this is about, when there is one.
87    pub fn program(&self) -> Option<&OsStr> {
88        match self {
89            Self::Start { program, .. } => Some(program),
90            _ => None,
91        }
92    }
93
94    /// Whether this is "there is no such program", which is a setup problem rather than a failure.
95    pub fn is_not_found(&self) -> bool {
96        matches!(
97            self,
98            Self::NotFound(_) | Self::NoProject(_) | Self::Setup(_)
99        )
100    }
101}
102
103impl fmt::Display for EngineError {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        f.write_str(&self.message())
106    }
107}
108
109impl std::error::Error for EngineError {
110    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
111        match self {
112            Self::Start { source, .. } => Some(source),
113            #[cfg(feature = "store")]
114            Self::Kit(error) => Some(error),
115            _ => None,
116        }
117    }
118}
119
120#[cfg(feature = "store")]
121impl From<crate_plugin_kit::KitError> for EngineError {
122    fn from(error: crate_plugin_kit::KitError) -> Self {
123        Self::Kit(error)
124    }
125}
126
127/// The crate's own result, so the ported helpers did not have to change shape.
128pub(crate) type Result<T> = std::result::Result<T, EngineError>;