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
15pub 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 package_url: Box<gix::Url>,
34 package_commit: ObjectId,
36 package_path: PathBuf,
38 attempted: PathBuf,
39 restriction: PathBuf,
40 },
41 RelativeGitImport {
44 path: PathBuf,
45 },
46 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 TempFilePersist {
61 error: tempfile::PersistError,
62 },
63 InvalidIndexDep {
67 id: index::Id,
68 dep: Box<UnversionedDependency>,
69 },
70 UnknownIndexPackage {
72 id: index::Id,
73 },
74 UnknownIndexPackageVersion {
76 id: index::Id,
77 requested: SemVer,
78 available: Vec<SemVer>,
79 },
80 DuplicateIndexPackageVersion {
83 id: index::Id,
84 version: SemVer,
85 },
86 PackageIndexSerialization {
88 pkg: crate::index::Package,
89 error: serde_json::Error,
90 },
91 PackageIndexDeserialization {
93 error: serde_json::Error,
94 },
95 Resolution(Box<ResolveError>),
96 MismatchedManifestPath {
99 id: index::Id,
100 manifest_dir: PathBuf,
101 },
102 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 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}