1use thiserror::Error;
2
3#[derive(Debug, Error)]
5#[non_exhaustive]
6pub enum WalkerError {
7 #[error("flake.nix is not a parseable Nix file")]
9 NotARoot,
10
11 #[error("unexpected non-attribute at top level of flake.nix at byte {offset}: {snippet}")]
15 UnexpectedTopLevel { snippet: String, offset: u32 },
16}
17
18impl WalkerError {
19 pub(crate) fn unexpected_top_level(text: &str, offset: u32) -> Self {
20 const MAX_SNIPPET: usize = 60;
21
22 let single_line: String = text
23 .replace(['\n', '\r'], " ")
24 .split_whitespace()
25 .collect::<Vec<_>>()
26 .join(" ");
27 let snippet = if single_line.chars().count() > MAX_SNIPPET {
28 let truncated: String = single_line.chars().take(MAX_SNIPPET).collect();
29 format!("{truncated}...")
30 } else {
31 single_line
32 };
33 Self::UnexpectedTopLevel { snippet, offset }
34 }
35}
36
37#[cfg(test)]
38mod tests {
39 use super::WalkerError;
40
41 #[test]
42 fn unexpected_top_level_truncates_long_snippets() {
43 let long = "x".repeat(200);
44 let err = WalkerError::unexpected_top_level(&long, 7);
45 let s = err.to_string();
46 assert!(s.contains("..."), "long snippet should be truncated: {s}");
47 assert!(s.contains("byte 7"), "byte offset should survive: {s}");
48 }
49
50 #[test]
51 fn unexpected_top_level_collapses_newlines() {
52 let err = WalkerError::unexpected_top_level("let\n x = 1;\nin x", 3);
53 let s = err.to_string();
54 assert!(!s.contains('\n'), "newlines should be collapsed: {s:?}");
55 assert!(s.ends_with("let x = 1; in x"));
56 }
57}