1use std::path::PathBuf;
7
8#[derive(Debug, thiserror::Error)]
11pub enum RepographError {
12 #[error("i/o error: {0}")]
14 Io(#[from] std::io::Error),
15
16 #[error("invalid TOML in config file: {0}")]
18 ConfigParse(#[from] toml::de::Error),
19
20 #[error("failed to serialize config: {0}")]
22 ConfigWrite(#[from] toml::ser::Error),
23
24 #[error("not a git repository: {path}: {source}")]
26 GitOpen {
27 path: PathBuf,
28 #[source]
29 source: git2::Error,
30 },
31
32 #[error("{kind} '{name}' not found")]
34 NotFound { kind: &'static str, name: String },
35
36 #[error("{kind} '{name}' already registered")]
38 Conflict { kind: &'static str, name: String },
39
40 #[error("permission denied: {path}")]
42 PermissionDenied { path: PathBuf },
43
44 #[error("{0}")]
47 UsageError(String),
48
49 #[error("invalid {kind} name '{name}': {reason}")]
53 InvalidName {
54 kind: &'static str,
55 name: String,
56 reason: &'static str,
57 },
58
59 #[error("{0}")]
65 NeedsInit(String),
66
67 #[error("doctor found {count} error finding(s) — see report above")]
74 DoctorErrorsFound { count: u32 },
75}
76
77impl RepographError {
78 #[must_use]
81 pub fn exit_code(&self) -> u8 {
82 match self {
83 Self::Io(e) if e.kind() == std::io::ErrorKind::PermissionDenied => 4,
84 Self::PermissionDenied { .. } => 4,
85 Self::GitOpen { .. } | Self::NotFound { .. } => 3,
86 Self::Conflict { .. } => 5,
87 Self::InvalidName { .. } | Self::NeedsInit { .. } | Self::UsageError(_) => 2,
88 Self::Io(_)
89 | Self::ConfigParse(_)
90 | Self::ConfigWrite(_)
91 | Self::DoctorErrorsFound { .. } => 1,
92 }
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 #![allow(clippy::unwrap_used)]
99 use super::*;
100
101 #[test]
102 fn io_permission_denied_maps_to_4() {
103 let err = RepographError::Io(std::io::Error::from(std::io::ErrorKind::PermissionDenied));
104 assert_eq!(err.exit_code(), 4);
105 }
106
107 #[test]
108 fn other_io_maps_to_1() {
109 let err = RepographError::Io(std::io::Error::from(std::io::ErrorKind::NotFound));
110 assert_eq!(err.exit_code(), 1);
111 }
112
113 #[test]
114 fn explicit_permission_denied_maps_to_4() {
115 let err = RepographError::PermissionDenied {
116 path: PathBuf::from("/nope"),
117 };
118 assert_eq!(err.exit_code(), 4);
119 }
120
121 #[test]
122 fn not_found_maps_to_3() {
123 let err = RepographError::NotFound {
124 kind: "repo",
125 name: "foo".into(),
126 };
127 assert_eq!(err.exit_code(), 3);
128 }
129
130 #[test]
131 fn git_open_maps_to_3() {
132 let err = RepographError::GitOpen {
133 path: PathBuf::from("/tmp/x"),
134 source: git2::Error::from_str("synthetic"),
135 };
136 assert_eq!(err.exit_code(), 3);
137 }
138
139 #[test]
140 fn conflict_maps_to_5() {
141 let err = RepographError::Conflict {
142 kind: "name",
143 name: "foo".into(),
144 };
145 assert_eq!(err.exit_code(), 5);
146 }
147
148 #[test]
149 fn usage_error_maps_to_2() {
150 let err = RepographError::UsageError("nope".into());
151 assert_eq!(err.exit_code(), 2);
152 }
153
154 #[test]
155 fn invalid_name_maps_to_2() {
156 let err = RepographError::InvalidName {
157 kind: "workspace",
158 name: "Bad Name".into(),
159 reason: "must be lowercase",
160 };
161 assert_eq!(err.exit_code(), 2);
162 }
163
164 #[test]
165 fn needs_init_maps_to_2() {
166 let err = RepographError::NeedsInit("agents not configured; run `repograph init`".into());
167 assert_eq!(err.exit_code(), 2);
168 assert!(err.to_string().contains("repograph init"));
169 }
170
171 #[test]
172 fn config_parse_maps_to_1() {
173 let err: RepographError = toml::from_str::<toml::Value>("[unterminated")
174 .unwrap_err()
175 .into();
176 assert_eq!(err.exit_code(), 1);
177 }
178}