1use std::path::PathBuf;
4
5#[derive(Debug)]
7#[non_exhaustive]
8pub enum Error {
9 EntryNotFound(PathBuf, std::io::Error),
11 EntryIsDirectory(PathBuf),
13 UnsupportedFileType(Option<String>),
15 EntryNotInGraph(PathBuf),
17 SnapshotRead(PathBuf, std::io::Error),
19 SnapshotParse(PathBuf, serde_json::Error),
21 SnapshotWrite(PathBuf, std::io::Error),
23 MutuallyExclusiveFlags(String),
25 TargetIsEntryPoint(String),
27 EntryRequired,
29 NotAGitRepo,
31 NotSnapshotOrRef(String),
33 DiffFileNotFound(String),
35 GitError(String),
37 InvalidTopValue(&'static str, i32),
39 Readline(String),
41 MaxWeightExceeded {
43 kind: &'static str,
44 weight: u64,
45 module_count: usize,
46 threshold: u64,
47 },
48}
49
50impl Error {
51 pub fn hint(&self) -> Option<&str> {
53 match self {
54 Self::UnsupportedFileType(_) => Some(
55 "chainsaw supports TypeScript/JavaScript (.ts, .tsx, .js, .jsx, .mjs, .cjs) and Python (.py) files",
56 ),
57 Self::EntryNotInGraph(_) => Some("is it reachable from the project root?"),
58 Self::TargetIsEntryPoint(flag) => Some(if flag == "--chain" {
59 "--chain finds import chains from the entry to a dependency"
60 } else {
61 "--cut finds where to sever import chains to a dependency"
62 }),
63 Self::EntryRequired => Some("use --entry to specify the entry point to trace"),
64 Self::EntryIsDirectory(_) => {
65 Some("provide a source file (e.g. src/index.ts or main.py)")
66 }
67 _ => None,
68 }
69 }
70}
71
72impl std::fmt::Display for Error {
75 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76 match self {
77 Self::EntryNotFound(path, source) => {
78 write!(f, "cannot find entry file '{}': {source}", path.display())
79 }
80 Self::EntryIsDirectory(path) => {
81 write!(f, "'{}' is a directory, not a source file", path.display())
82 }
83 Self::UnsupportedFileType(Some(ext)) => {
84 write!(f, "unsupported file type '.{ext}'")
85 }
86 Self::UnsupportedFileType(None) => {
87 write!(f, "file has no extension")
88 }
89 Self::EntryNotInGraph(path) => {
90 write!(f, "entry file '{}' not found in graph", path.display())
91 }
92 Self::SnapshotRead(path, source) => {
93 write!(f, "cannot read snapshot '{}': {source}", path.display())
94 }
95 Self::SnapshotParse(path, source) => {
96 write!(f, "invalid snapshot '{}': {source}", path.display())
97 }
98 Self::SnapshotWrite(path, source) => {
99 write!(f, "cannot write snapshot '{}': {source}", path.display())
100 }
101 Self::MutuallyExclusiveFlags(flags) => {
102 write!(f, "{flags} cannot be used together")
103 }
104 Self::TargetIsEntryPoint(flag) => {
105 write!(f, "{flag} target is the entry point itself")
106 }
107 Self::EntryRequired => {
108 write!(
109 f,
110 "--entry is required when diffing against a git ref or the working tree"
111 )
112 }
113 Self::NotAGitRepo => write!(f, "not inside a git repository"),
114 Self::NotSnapshotOrRef(arg) => {
115 write!(f, "'{arg}' is not a snapshot file or a valid git ref")
116 }
117 Self::DiffFileNotFound(arg) => write!(f, "file not found: {arg}"),
118 Self::GitError(msg) => write!(f, "git: {msg}"),
119 Self::InvalidTopValue(flag, n) => {
120 write!(f, "invalid value {n} for {flag}: must be -1 (all) or 0+")
121 }
122 Self::Readline(msg) => write!(f, "readline: {msg}"),
123 Self::MaxWeightExceeded {
124 kind,
125 weight,
126 module_count,
127 threshold,
128 } => {
129 let plural = if *module_count == 1 { "" } else { "s" };
130 write!(
131 f,
132 "{kind} transitive weight {} ({module_count} module{plural}) exceeds --max-weight threshold {}",
133 crate::report::format_size(*weight),
134 crate::report::format_size(*threshold),
135 )
136 }
137 }
138 }
139}
140
141impl std::error::Error for Error {
143 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
144 match self {
145 Self::EntryNotFound(_, e) | Self::SnapshotRead(_, e) | Self::SnapshotWrite(_, e) => {
146 Some(e)
147 }
148 Self::SnapshotParse(_, e) => Some(e),
149 _ => None,
150 }
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
159 fn unsupported_file_type_no_extension() {
160 let err = Error::UnsupportedFileType(None);
161 assert!(err.to_string().contains("no extension"));
162 assert!(err.hint().unwrap().contains(".ts"));
163 }
164
165 #[test]
166 fn unsupported_file_type_with_extension() {
167 let err = Error::UnsupportedFileType(Some("rs".to_string()));
168 assert!(err.to_string().contains(".rs"));
169 }
170
171 #[test]
172 fn entry_is_directory_has_hint() {
173 let err = Error::EntryIsDirectory(PathBuf::from("/tmp/src"));
174 assert!(err.hint().unwrap().contains("source file"));
175 }
176}