Skip to main content

flake_edit/
error.rs

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