Skip to main content

elfpak_core/
error.rs

1//! Structured errors for `elfpak`.
2//!
3//! Every variant carries a stable diagnostic code so that the CLI can render
4//! `error[E2001]`-style messages and scripts can match on them.
5
6use std::path::PathBuf;
7
8pub type Result<T> = std::result::Result<T, Error>;
9
10#[derive(Debug, thiserror::Error)]
11pub enum Error {
12    #[error("io error on `{path}`: {source}")]
13    Io {
14        path: PathBuf,
15        #[source]
16        source: std::io::Error,
17    },
18
19    #[error("`{path}` is not a valid ELF object: {message}")]
20    Elf { path: PathBuf, message: String },
21
22    #[error("`{path}` is not an ELF file")]
23    NotElf { path: PathBuf },
24
25    #[error(
26        "`{path}` targets an unsupported architecture: {architecture} (e_machine = {machine:#x})"
27    )]
28    UnsupportedArchitecture {
29        path: PathBuf,
30        architecture: String,
31        machine: u16,
32    },
33
34    #[error("unable to resolve shared library `{soname}`")]
35    UnresolvedLibrary {
36        soname: String,
37        required_by: PathBuf,
38        searched: Vec<PathBuf>,
39    },
40
41    #[error("library `{soname}` is not allowed by dependency policy")]
42    DisallowedLibrary {
43        soname: String,
44        required_by: PathBuf,
45    },
46
47    #[error("resolved library `{soname}` has an incompatible architecture")]
48    IncompatibleArchitecture {
49        soname: String,
50        expected: String,
51        found: PathBuf,
52        found_architecture: String,
53    },
54
55    #[error("runtime policy feature `{feature}` could not be satisfied")]
56    MissingRuntimeFile {
57        feature: &'static str,
58        searched: Vec<PathBuf>,
59    },
60
61    #[error("{resource} exceeds the supported limit of {limit}")]
62    LimitExceeded {
63        resource: &'static str,
64        limit: usize,
65    },
66
67    #[error(
68        "source file `{path}` changed after it was added to the bundle plan (expected {expected_size} bytes with sha256 {expected_digest}, found {actual_size} bytes with sha256 {actual_digest})"
69    )]
70    SourceChanged {
71        path: PathBuf,
72        expected_digest: String,
73        expected_size: u64,
74        actual_digest: String,
75        actual_size: u64,
76    },
77
78    #[error("path `{path}` escapes the {kind} root")]
79    PathEscape { path: PathBuf, kind: &'static str },
80
81    #[error("`{path}` does not exist inside the source root")]
82    MissingSourcePath { path: PathBuf },
83
84    #[error("too many levels of symbolic links while resolving `{path}`")]
85    SymlinkLoop { path: PathBuf },
86
87    #[error("invalid configuration: {message}")]
88    Config { message: String },
89
90    #[error("invalid manifest `{path}`: {message}")]
91    Manifest { path: PathBuf, message: String },
92
93    #[error("verification failed: {failures} problem(s) across {checked} manifest entries")]
94    VerifyFailed { checked: u32, failures: u32 },
95}
96
97impl Error {
98    /// Stable diagnostic code, rendered as `error[E1001]` by the CLI.
99    ///
100    /// The codes live in [`crate::diagnostics`], next to the warning codes, so
101    /// that the whole namespace is visible in one place and checked there.
102    pub fn code(&self) -> &'static str {
103        use crate::diagnostics::error as code;
104        match self {
105            Error::Io { .. } => code::IO,
106            Error::Elf { .. } => code::ELF,
107            Error::NotElf { .. } => code::NOT_ELF,
108            Error::UnsupportedArchitecture { .. } => code::UNSUPPORTED_ARCHITECTURE,
109            Error::LimitExceeded { .. } => code::LIMIT_EXCEEDED,
110            Error::SourceChanged { .. } => code::SOURCE_CHANGED,
111            Error::UnresolvedLibrary { .. } => code::UNRESOLVED_LIBRARY,
112            Error::DisallowedLibrary { .. } => code::DISALLOWED_LIBRARY,
113            Error::IncompatibleArchitecture { .. } => code::INCOMPATIBLE_ARCHITECTURE,
114            Error::MissingRuntimeFile { .. } => code::MISSING_RUNTIME_FILE,
115            Error::PathEscape { .. } => code::PATH_ESCAPE,
116            Error::MissingSourcePath { .. } => code::MISSING_SOURCE_PATH,
117            Error::SymlinkLoop { .. } => code::SYMLINK_LOOP,
118            Error::Config { .. } => code::CONFIG,
119            Error::Manifest { .. } => code::MANIFEST,
120            Error::VerifyFailed { .. } => code::VERIFY_FAILED,
121        }
122    }
123
124    /// Extra context printed underneath the headline message, if there is any.
125    pub fn details(&self) -> Vec<String> {
126        match self {
127            Error::UnresolvedLibrary {
128                required_by,
129                searched,
130                ..
131            } => {
132                let mut out = vec![format!("required by:\n  {}", required_by.display())];
133                if !searched.is_empty() {
134                    let list = searched
135                        .iter()
136                        .map(|p| format!("  {}", p.display()))
137                        .collect::<Vec<_>>()
138                        .join("\n");
139                    out.push(format!("searched:\n{list}"));
140                }
141                out
142            }
143            Error::DisallowedLibrary {
144                soname,
145                required_by,
146            } => vec![
147                format!("required by:\n  {}", required_by.display()),
148                format!("add:\n  --allow-library {soname}"),
149            ],
150            Error::IncompatibleArchitecture {
151                soname,
152                expected,
153                found,
154                found_architecture,
155            } => vec![
156                format!("requested:\n  {soname} for {expected}"),
157                format!("found:\n  {} ({found_architecture})", found.display()),
158            ],
159            Error::UnsupportedArchitecture { .. } => {
160                vec!["supported:\n  x86_64\n  aarch64".to_string()]
161            }
162            Error::MissingRuntimeFile { searched, .. } if !searched.is_empty() => {
163                let list = searched
164                    .iter()
165                    .map(|p| format!("  {}", p.display()))
166                    .collect::<Vec<_>>()
167                    .join("\n");
168                vec![format!("searched:\n{list}")]
169            }
170            _ => Vec::new(),
171        }
172    }
173}
174
175pub(crate) fn io(path: impl Into<PathBuf>, source: std::io::Error) -> Error {
176    Error::Io {
177        path: path.into(),
178        source,
179    }
180}