1use std::path::PathBuf;
2
3use crate::change::ChangeId;
4use crate::config::ConfigError;
5use crate::follows::path::AttrPathParseError;
6use crate::validate::ValidationError;
7
8#[derive(Debug, thiserror::Error)]
13#[non_exhaustive]
14pub enum Error {
15 #[error(transparent)]
17 Flake(#[from] crate::Error),
18
19 #[error(transparent)]
21 Config(#[from] ConfigError),
22
23 #[error("io error: {0}")]
25 Io(#[from] std::io::Error),
26
27 #[error("could not open flake.nix at {path}", path = path.display())]
29 FlakeNotFound {
30 path: PathBuf,
31 #[source]
32 source: std::io::Error,
33 },
34
35 #[error("no flake.nix in directory {path}", path = path.display())]
37 FlakeDirEmpty { path: PathBuf },
38
39 #[error("`--flake` and `--lock` cannot be used with `follow [PATHS]`")]
42 IncompatibleFollowOptions,
43
44 #[error("no URI provided")]
46 NoUri,
47
48 #[error("no input id provided")]
50 NoId,
51
52 #[error("no inputs found in the flake")]
54 NoInputs,
55
56 #[error("invalid URI '{uri}'")]
58 InvalidUri {
59 uri: String,
60 #[source]
61 source: nix_uri::NixUriError,
62 },
63
64 #[error("invalid input id '{id}'")]
66 InvalidInputId {
67 id: String,
68 #[source]
69 source: AttrPathParseError,
70 },
71
72 #[error("invalid follows path '{path}'")]
74 InvalidFollowsPath {
75 path: String,
76 #[source]
77 source: AttrPathParseError,
78 },
79
80 #[error("could not infer id from flake reference '{uri}'")]
82 CouldNotInferId { uri: String },
83
84 #[error("input '{id}' has no pinnable URL (it may use follows or a non-standard format)")]
87 InputNotPinnable { id: String },
88
89 #[error("could not remove input '{id}'")]
91 CouldNotRemove { id: ChangeId },
92
93 #[error("could not read lock file '{path}'", path = path.display())]
96 LockFile {
97 path: PathBuf,
98 #[source]
99 source: crate::Error,
100 },
101
102 #[error("could not create follows relationship for '{id}'")]
105 FollowsCreateFailed { id: String },
106
107 #[error("validation failed after applying edits ({} issue(s))", .0.len())]
111 ValidationAfterEdit(Vec<ValidationError>),
112
113 #[error("{} file(s) failed during batch processing", failures.len())]
116 Batch {
117 failures: Vec<(PathBuf, Box<Error>)>,
118 },
119
120 #[error("no toggleable inputs in flake.nix")]
123 NoToggleableInputs,
124
125 #[error("multiple toggleable inputs")]
128 MultipleToggleableInputs { candidates: Vec<ToggleCandidate> },
129
130 #[error("no input named '{id}' in flake.nix")]
132 ToggleUnknownInput { id: String },
133
134 #[error("input '{id}' has no stored alternate")]
136 ToggleNoAlternate { id: String },
137
138 #[error(
141 "input '{id}' has {count} variants, cannot infer which to {action}",
142 count = alternates.len() + 1
143 )]
144 ToggleAmbiguousVariant {
145 id: String,
146 active: String,
147 alternates: Vec<String>,
148 action: ToggleAction,
149 },
150
151 #[error("could not match '{reference}' to any input")]
153 ToggleRefUnmatched { reference: String },
154
155 #[error("'{reference}' matches multiple inputs")]
158 ToggleRefAmbiguous {
159 reference: String,
160 candidates: Vec<RefCandidate>,
161 },
162
163 #[error("could not read path '{path}'")]
165 TogglePathMissing {
166 path: String,
167 #[source]
168 source: std::io::Error,
169 },
170
171 #[error(
174 "'{reference}' is already the active variant of '{id}'\n and no alternate is stored"
175 )]
176 ToggleAlreadyActive { reference: String, id: String },
177
178 #[error("'{reference}' is not a stored variant of '{id}'")]
181 ToggleRemoveUnstored {
182 reference: String,
183 id: String,
184 active: String,
185 alternates: Vec<String>,
186 },
187
188 #[error("'{reference}' is the active url of '{id}' and no alternate is stored to replace it")]
192 ToggleRemoveActive { reference: String, id: String },
193}
194
195#[derive(Debug, Clone, Copy, PartialEq, Eq)]
197pub enum ToggleAction {
198 Activate,
200 Remove,
202}
203
204impl std::fmt::Display for ToggleAction {
205 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
206 match self {
207 Self::Activate => write!(f, "activate"),
208 Self::Remove => write!(f, "remove"),
209 }
210 }
211}
212
213#[derive(Debug, Clone)]
215pub struct ToggleCandidate {
216 pub id: String,
218 pub variants: Vec<String>,
220}
221
222#[derive(Debug, Clone)]
225pub struct RefCandidate {
226 pub id: String,
228 pub reason: String,
230}
231
232impl Error {
233 pub fn batch_bullets(&self) -> Option<Vec<String>> {
237 match self {
238 Self::Batch { failures } => Some(
239 failures
240 .iter()
241 .map(|(path, err)| {
242 format!(
243 "{}: {}",
244 path.display(),
245 chain_layers(err.as_ref()).join(": ")
246 )
247 })
248 .collect(),
249 ),
250 _ => None,
251 }
252 }
253
254 pub fn validation_bullets(&self) -> Option<Vec<String>> {
257 match self {
258 Self::ValidationAfterEdit(errs) => Some(errs.iter().map(|e| e.to_string()).collect()),
259 _ => None,
260 }
261 }
262
263 pub fn candidate_bullets(&self) -> Option<Vec<String>> {
266 match self {
267 Self::MultipleToggleableInputs { candidates } => Some(
268 candidates
269 .iter()
270 .map(|c| format!("{}: {}", c.id, c.variants.join(" <-> ")))
271 .collect(),
272 ),
273 Self::ToggleAmbiguousVariant {
274 active, alternates, ..
275 }
276 | Self::ToggleRemoveUnstored {
277 active, alternates, ..
278 } => Some(
279 std::iter::once(format!("{active} (active)"))
280 .chain(alternates.iter().cloned())
281 .collect(),
282 ),
283 Self::ToggleRefAmbiguous { candidates, .. } => Some(
284 candidates
285 .iter()
286 .map(|c| format!("{} ({})", c.id, c.reason))
287 .collect(),
288 ),
289 _ => None,
290 }
291 }
292}
293
294pub fn chain_layers(err: &(dyn std::error::Error + 'static)) -> Vec<String> {
302 let mut layers = vec![err.to_string()];
303 let mut current = err.source();
304 while let Some(source) = current {
305 layers.push(source.to_string());
306 current = source.source();
307 }
308 layers
309}
310
311pub type Result<T> = std::result::Result<T, Error>;