1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
use std::path::PathBuf;
use crate::lock::LockError;
use crate::validate::ValidationError;
use crate::walk::WalkerError;
/// Error for [`crate::edit::FlakeEdit`] operations.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
/// Failed to read a flake file. Carries the path that the read was
/// attempted against so the caller can surface it.
#[error("failed to read {path}", path = path.display())]
Read {
path: PathBuf,
#[source]
source: std::io::Error,
},
/// Failed to write a flake file.
#[error("failed to write {path}", path = path.display())]
Write {
path: PathBuf,
#[source]
source: std::io::Error,
},
/// The CST walker rejected a change. See [`WalkerError`] for details.
#[error(transparent)]
Walker(#[from] WalkerError),
/// A failure parsing or walking `flake.lock`. See [`LockError`] for the
/// per-variant breakdown.
#[error(transparent)]
Lock(#[from] LockError),
/// Tried to add an input that already exists. The wrapped string is the
/// existing input id.
#[error("input '{0}' already exists in the flake")]
DuplicateInput(String),
/// Tried to operate on an input id that is not declared in the flake.
#[error("input '{0}' not found in the flake")]
InputNotFound(String),
/// The `add-follow` subcommand received a path deeper than `parent.child`.
/// `flake-edit follow` accepts deeper paths, bounded by
/// [`crate::config::FollowConfig::max_depth`] when that is set; this
/// guard catches typos in the explicit-path command before they produce
/// nested `inputs.*.inputs.*.follows` chains.
#[error(
"`add-follow` accepts only depth-1 paths of the form `parent.child`; got '{path}' ({segments} segments)"
)]
AddFollowDepthLimit { path: String, segments: usize },
/// Pre-edit validation found one or more fatal issues in `flake.nix`.
#[error("validation failed in flake.nix ({} issue(s))", .0.len())]
Validation(Vec<ValidationError>),
}
impl Error {
/// Actionable hint to display alongside the error, when one exists.
///
/// Hints live here rather than in `#[error(...)]` strings so the binary
/// can render them on a separate `hint:` line and library callers can
/// choose to surface or ignore them.
pub fn hint(&self) -> Option<String> {
match self {
Self::DuplicateInput(id) => Some(format!(
"to replace it, run `flake-edit remove {id}` then `flake-edit add {id} <flakeref>`; \
or add it under a different id with `flake-edit add [ID] <flakeref>`"
)),
Self::InputNotFound(id) => Some(format!(
"to add it, run `flake-edit add {id} <flakeref>`; \
see declared inputs with `flake-edit list`"
)),
Self::AddFollowDepthLimit { .. } => Some(
"use `flake-edit follow` for deeper paths (depth bounded by `follow.max_depth` in your config, if set)"
.into(),
),
_ => None,
}
}
/// Per-error rendering of a `Validation` aggregate as one bullet per
/// inner error. Returns `None` for non-aggregate variants.
pub fn bullets(&self) -> Option<Vec<String>> {
match self {
Self::Validation(errors) => Some(errors.iter().map(|e| e.to_string()).collect()),
_ => None,
}
}
}