Skip to main content

nickel_lang_package/
error.rs

1use std::path::{Path, PathBuf};
2
3use gix::ObjectId;
4use nickel_lang_core::{
5    error::INTERNAL_ERROR_MSG, files::Files, identifier::Ident, program::BuilderError,
6};
7
8use crate::{
9    UnversionedDependency,
10    index::{self, path::RelativePathError},
11    resolve::ResolveError,
12    version::SemVer,
13};
14
15/// Errors related to package management.
16pub enum Error {
17    Io {
18        path: Option<PathBuf>,
19        error: std::io::Error,
20    },
21    LockFileDeserialization {
22        path: PathBuf,
23        error: serde_json::Error,
24    },
25    ManifestEval {
26        package: Option<Ident>,
27        files: Files,
28        error: Box<nickel_lang_core::error::Error>,
29    },
30    NoProjectDir,
31    RestrictedPath {
32        /// The url of the git package that tried the bad import.
33        package_url: Box<gix::Url>,
34        /// The git id of the bad package.
35        package_commit: ObjectId,
36        /// The relative path of the bad package within its git repo.
37        package_path: PathBuf,
38        attempted: PathBuf,
39        restriction: PathBuf,
40    },
41    /// There was an attempt to import a git repository from a relative path,
42    /// where the package doing the importing wasn't a path package.
43    RelativeGitImport {
44        path: PathBuf,
45    },
46    /// There was some error interacting with a git repository.
47    Git(nickel_lang_git::Error),
48    InvalidUrl {
49        msg: String,
50    },
51    InvalidPathInIndexPackage {
52        id: String,
53        inner: RelativePathError,
54    },
55    InternalManifestError {
56        path: PathBuf,
57        msg: String,
58    },
59    /// There was an error persisting a temporary file.
60    TempFilePersist {
61        error: tempfile::PersistError,
62    },
63    /// A package in the index (or, hopefully, a package potentially destined for
64    /// the index, because packages actually *in* the index should be validated)
65    /// tried to depend on a path or git dependency.
66    InvalidIndexDep {
67        id: index::Id,
68        dep: Box<UnversionedDependency>,
69    },
70    /// The package `id` wasn't found in the package index.
71    UnknownIndexPackage {
72        id: index::Id,
73    },
74    /// The requested version of package `id` wasn't found in the package index.
75    UnknownIndexPackageVersion {
76        id: index::Id,
77        requested: SemVer,
78        available: Vec<SemVer>,
79    },
80    /// While trying to insert a package in the index, we found that that same
81    /// package and version was already present.
82    DuplicateIndexPackageVersion {
83        id: index::Id,
84        version: SemVer,
85    },
86    /// We failed to serialize the index description of a package.
87    PackageIndexSerialization {
88        pkg: crate::index::Package,
89        error: serde_json::Error,
90    },
91    /// We failed to deserialize the index description of a package.
92    PackageIndexDeserialization {
93        error: serde_json::Error,
94    },
95    Resolution(Box<ResolveError>),
96    /// Based on the index id, we expected a manifest file to have a path
97    /// ending with a particular suffix but it didn't.
98    MismatchedManifestPath {
99        id: index::Id,
100        manifest_dir: PathBuf,
101    },
102    /// Some other error interacting with git.
103    ///
104    /// gix's errors are highly structured, and for many of them we only
105    /// care about reporting them as strings.
106    OtherGit(anyhow::Error),
107}
108
109impl std::error::Error for Error {}
110
111impl std::fmt::Debug for Error {
112    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113        std::fmt::Display::fmt(self, f)
114    }
115}
116
117impl std::fmt::Display for Error {
118    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119        match self {
120            Error::Io { error, path } => {
121                if let Some(path) = path {
122                    write!(f, "{}: {error}", path.display())
123                } else {
124                    error.fmt(f)
125                }
126            }
127            // Just a short and not-very-informative error. To write a better error message to
128            // the terminal, use `program.report` like the cli does.
129            Error::ManifestEval { package, .. } => {
130                if let Some(package) = package {
131                    write!(f, "error evaluating manifest for package {package}")
132                } else {
133                    write!(f, "error evaluating package manifest")
134                }
135            }
136            Error::RestrictedPath {
137                attempted,
138                restriction,
139                package_url,
140                package_commit,
141                package_path,
142            } => {
143                write!(
144                    f,
145                    "git package {package_url}@{package_commit}/{} tried to import path {}, but can only import from {}",
146                    package_path.display(),
147                    attempted.display(),
148                    restriction.display()
149                )
150            }
151            Error::Git(e) => e.fmt(f),
152            Error::InvalidUrl { msg, .. } => {
153                write!(f, "{msg}")
154            }
155            Error::InternalManifestError { path, msg } => {
156                write!(
157                    f,
158                    "internal error reading the manifest at {}: {msg}\n{INTERNAL_ERROR_MSG}",
159                    path.display()
160                )
161            }
162            Error::RelativeGitImport { path } => write!(
163                f,
164                "tried to import a relative git path ({}), but we have no root",
165                path.display()
166            ),
167            Error::TempFilePersist { error } => error.fmt(f),
168            Error::NoProjectDir => {
169                write!(
170                    f,
171                    "failed to find a location for the nickel cache directory"
172                )
173            }
174            Error::LockFileDeserialization { path, error } => {
175                write!(f, "lock file {} is invalid: {error}", path.display())
176            }
177            Error::UnknownIndexPackage { id } => write!(f, "package {id} not found in the index"),
178            Error::UnknownIndexPackageVersion {
179                id,
180                requested,
181                available,
182            } => {
183                let available: Vec<_> = available.iter().map(|x| x.to_string()).collect();
184                write!(
185                    f,
186                    "package {id}@{requested} not found in the index. Available versions: {}",
187                    available.join(", ")
188                )
189            }
190            Error::InvalidIndexDep { id, dep } => match dep.as_ref() {
191                UnversionedDependency::Git(g) => write!(
192                    f,
193                    "package {id} depends on git package {}, so it cannot be put in the index",
194                    g.url
195                ),
196                UnversionedDependency::Path(path) => write!(
197                    f,
198                    "package {id} depends on path package {}, so it cannot be put in the index",
199                    path.display()
200                ),
201            },
202            Error::DuplicateIndexPackageVersion { id, version } => {
203                write!(f, "package {id}@{version} is already present in the index")
204            }
205            Error::PackageIndexSerialization { error, pkg } => {
206                write!(
207                    f,
208                    "error serializing package {pkg:?}, caused by {error}\n{INTERNAL_ERROR_MSG}"
209                )
210            }
211            Error::PackageIndexDeserialization { error } => {
212                write!(
213                    f,
214                    "error deserializing package: {error}\n{INTERNAL_ERROR_MSG}"
215                )
216            }
217            Error::OtherGit(error) => {
218                write!(f, "{error}")
219            }
220            Error::Resolution(e) => {
221                writeln!(f, "package version resolution failed:")?;
222                crate::resolve::print_resolve_error(f, e)
223            }
224            Error::InvalidPathInIndexPackage { inner, id } => {
225                writeln!(f, "invalid path in package {id}: {inner}")
226            }
227            Error::MismatchedManifestPath { id, manifest_dir } => {
228                writeln!(
229                    f,
230                    "package with id {id} has manifest at {}, but the paths don't match",
231                    manifest_dir.display()
232                )
233            }
234        }
235    }
236}
237
238pub trait ResultExt {
239    type T;
240    fn in_package(self, package: Ident) -> Result<Self::T, Error>;
241}
242
243impl<T> ResultExt for Result<T, Error> {
244    type T = T;
245
246    fn in_package(self, package: Ident) -> Result<Self::T, Error> {
247        self.map_err(|e| match e {
248            Error::ManifestEval { files, error, .. } => Error::ManifestEval {
249                package: Some(package),
250                files,
251                error,
252            },
253            x => x,
254        })
255    }
256}
257
258pub trait IoResultExt {
259    type T;
260    fn with_path(self, path: impl AsRef<Path>) -> Result<Self::T, Error>;
261    fn without_path(self) -> Result<Self::T, Error>;
262}
263
264impl<T> IoResultExt for Result<T, std::io::Error> {
265    type T = T;
266    fn with_path(self, path: impl AsRef<Path>) -> Result<T, Error> {
267        self.map_err(|e| Error::Io {
268            path: Some(path.as_ref().to_owned()),
269            error: e,
270        })
271    }
272
273    fn without_path(self) -> Result<T, Error> {
274        self.map_err(|e| Error::Io {
275            path: None,
276            error: e,
277        })
278    }
279}
280
281impl From<nickel_lang_git::Error> for Error {
282    fn from(e: nickel_lang_git::Error) -> Self {
283        Self::Git(e)
284    }
285}
286
287impl From<tempfile::PersistError> for Error {
288    fn from(error: tempfile::PersistError) -> Self {
289        Self::TempFilePersist { error }
290    }
291}
292
293impl From<gix::url::parse::Error> for Error {
294    fn from(e: gix::url::parse::Error) -> Self {
295        Self::InvalidUrl { msg: e.to_string() }
296    }
297}
298
299impl From<gix::open::Error> for Error {
300    fn from(e: gix::open::Error) -> Self {
301        Self::OtherGit(e.into())
302    }
303}
304
305impl From<gix::reference::head_tree_id::Error> for Error {
306    fn from(e: gix::reference::head_tree_id::Error) -> Self {
307        Self::OtherGit(e.into())
308    }
309}
310
311impl From<BuilderError> for Error {
312    fn from(error: BuilderError) -> Error {
313        match error {
314            BuilderError::NoInputs => Error::Io {
315                path: None,
316                error: std::io::Error::other("ProgramBuilder::build: no inputs were added"),
317            },
318            BuilderError::Io { path, error } => Error::Io { path, error },
319        }
320    }
321}