Skip to main content

coven_ssh/
error.rs

1// ABOUTME: Error types for SSH key operations using thiserror.
2// ABOUTME: Provides typed errors for key loading, generation, signing, and auth.
3
4use std::path::PathBuf;
5use thiserror::Error;
6
7/// Errors that can occur during SSH key operations.
8#[derive(Error, Debug)]
9pub enum SshError {
10    /// Failed to read a key file from disk.
11    #[error("failed to read SSH key from {path}: {source}")]
12    ReadKey {
13        path: PathBuf,
14        #[source]
15        source: std::io::Error,
16    },
17
18    /// Failed to parse an SSH key.
19    #[error("failed to parse SSH key from {path}: {source}")]
20    ParseKey {
21        path: PathBuf,
22        #[source]
23        source: ssh_key::Error,
24    },
25
26    /// Failed to generate an SSH key.
27    #[error("failed to generate SSH key: {0}")]
28    GenerateKey(#[source] ssh_key::Error),
29
30    /// Failed to serialize a key.
31    #[error("failed to serialize key: {0}")]
32    SerializeKey(#[source] ssh_key::Error),
33
34    /// Failed to write a key file to disk.
35    #[error("failed to write key to {path}: {source}")]
36    WriteKey {
37        path: PathBuf,
38        #[source]
39        source: std::io::Error,
40    },
41
42    /// Failed to create a directory.
43    #[error("failed to create directory {path}: {source}")]
44    CreateDirectory {
45        path: PathBuf,
46        #[source]
47        source: std::io::Error,
48    },
49
50    /// Failed to set file permissions.
51    #[error("failed to set permissions on {path}: {source}")]
52    SetPermissions {
53        path: PathBuf,
54        #[source]
55        source: std::io::Error,
56    },
57
58    /// Unsupported key type for the requested operation.
59    #[error("unsupported key type: {0} (only ed25519 is supported)")]
60    UnsupportedKeyType(String),
61
62    /// Failed to add metadata to gRPC request.
63    #[error("invalid metadata value for {field}: {message}")]
64    InvalidMetadata { field: String, message: String },
65}
66
67/// Result type alias using SshError.
68pub type Result<T> = std::result::Result<T, SshError>;
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73    use std::io;
74
75    #[test]
76    fn test_read_key_error_display() {
77        let err = SshError::ReadKey {
78            path: PathBuf::from("/path/to/key"),
79            source: io::Error::new(io::ErrorKind::NotFound, "file not found"),
80        };
81        let display = format!("{}", err);
82        assert!(display.contains("failed to read SSH key"));
83        assert!(display.contains("/path/to/key"));
84    }
85
86    #[test]
87    fn test_parse_key_error_display() {
88        // Create a mock ssh_key::Error by creating a parse error
89        let err = SshError::ParseKey {
90            path: PathBuf::from("/path/to/invalid_key"),
91            source: ssh_key::Error::AlgorithmUnknown,
92        };
93        let display = format!("{}", err);
94        assert!(display.contains("failed to parse SSH key"));
95        assert!(display.contains("/path/to/invalid_key"));
96    }
97
98    #[test]
99    fn test_generate_key_error_display() {
100        let err = SshError::GenerateKey(ssh_key::Error::AlgorithmUnknown);
101        let display = format!("{}", err);
102        assert!(display.contains("failed to generate SSH key"));
103    }
104
105    #[test]
106    fn test_serialize_key_error_display() {
107        let err = SshError::SerializeKey(ssh_key::Error::AlgorithmUnknown);
108        let display = format!("{}", err);
109        assert!(display.contains("failed to serialize key"));
110    }
111
112    #[test]
113    fn test_write_key_error_display() {
114        let err = SshError::WriteKey {
115            path: PathBuf::from("/path/to/key"),
116            source: io::Error::new(io::ErrorKind::PermissionDenied, "access denied"),
117        };
118        let display = format!("{}", err);
119        assert!(display.contains("failed to write key"));
120        assert!(display.contains("/path/to/key"));
121    }
122
123    #[test]
124    fn test_create_directory_error_display() {
125        let err = SshError::CreateDirectory {
126            path: PathBuf::from("/path/to/dir"),
127            source: io::Error::new(io::ErrorKind::PermissionDenied, "access denied"),
128        };
129        let display = format!("{}", err);
130        assert!(display.contains("failed to create directory"));
131        assert!(display.contains("/path/to/dir"));
132    }
133
134    #[test]
135    fn test_set_permissions_error_display() {
136        let err = SshError::SetPermissions {
137            path: PathBuf::from("/path/to/key"),
138            source: io::Error::new(io::ErrorKind::PermissionDenied, "access denied"),
139        };
140        let display = format!("{}", err);
141        assert!(display.contains("failed to set permissions"));
142        assert!(display.contains("/path/to/key"));
143    }
144
145    #[test]
146    fn test_unsupported_key_type_error_display() {
147        let err = SshError::UnsupportedKeyType("rsa".to_string());
148        let display = format!("{}", err);
149        assert!(display.contains("unsupported key type"));
150        assert!(display.contains("rsa"));
151        assert!(display.contains("only ed25519 is supported"));
152    }
153
154    #[test]
155    fn test_invalid_metadata_error_display() {
156        let err = SshError::InvalidMetadata {
157            field: "x-ssh-pubkey".to_string(),
158            message: "invalid header value".to_string(),
159        };
160        let display = format!("{}", err);
161        assert!(display.contains("invalid metadata value"));
162        assert!(display.contains("x-ssh-pubkey"));
163        assert!(display.contains("invalid header value"));
164    }
165
166    #[test]
167    fn test_error_debug() {
168        let err = SshError::UnsupportedKeyType("test".to_string());
169        let debug_str = format!("{:?}", err);
170        assert!(debug_str.contains("UnsupportedKeyType"));
171        assert!(debug_str.contains("test"));
172    }
173
174    #[test]
175    fn test_error_source_read_key() {
176        use std::error::Error;
177
178        let io_err = io::Error::new(io::ErrorKind::NotFound, "not found");
179        let err = SshError::ReadKey {
180            path: PathBuf::from("/path"),
181            source: io_err,
182        };
183
184        // Verify source() returns the underlying error
185        assert!(err.source().is_some());
186    }
187
188    #[test]
189    fn test_error_source_write_key() {
190        use std::error::Error;
191
192        let io_err = io::Error::new(io::ErrorKind::PermissionDenied, "denied");
193        let err = SshError::WriteKey {
194            path: PathBuf::from("/path"),
195            source: io_err,
196        };
197
198        assert!(err.source().is_some());
199    }
200
201    #[test]
202    fn test_error_source_create_directory() {
203        use std::error::Error;
204
205        let io_err = io::Error::new(io::ErrorKind::PermissionDenied, "denied");
206        let err = SshError::CreateDirectory {
207            path: PathBuf::from("/path"),
208            source: io_err,
209        };
210
211        assert!(err.source().is_some());
212    }
213
214    #[test]
215    fn test_error_source_set_permissions() {
216        use std::error::Error;
217
218        let io_err = io::Error::other("error");
219        let err = SshError::SetPermissions {
220            path: PathBuf::from("/path"),
221            source: io_err,
222        };
223
224        assert!(err.source().is_some());
225    }
226
227    #[test]
228    fn test_error_source_generate_key() {
229        use std::error::Error;
230
231        let err = SshError::GenerateKey(ssh_key::Error::AlgorithmUnknown);
232        assert!(err.source().is_some());
233    }
234
235    #[test]
236    fn test_error_source_serialize_key() {
237        use std::error::Error;
238
239        let err = SshError::SerializeKey(ssh_key::Error::AlgorithmUnknown);
240        assert!(err.source().is_some());
241    }
242
243    #[test]
244    fn test_error_source_parse_key() {
245        use std::error::Error;
246
247        let err = SshError::ParseKey {
248            path: PathBuf::from("/path"),
249            source: ssh_key::Error::AlgorithmUnknown,
250        };
251        assert!(err.source().is_some());
252    }
253
254    #[test]
255    fn test_error_no_source_unsupported_key_type() {
256        use std::error::Error;
257
258        let err = SshError::UnsupportedKeyType("rsa".to_string());
259        // UnsupportedKeyType has no source
260        assert!(err.source().is_none());
261    }
262
263    #[test]
264    fn test_error_no_source_invalid_metadata() {
265        use std::error::Error;
266
267        let err = SshError::InvalidMetadata {
268            field: "x-ssh-pubkey".to_string(),
269            message: "invalid".to_string(),
270        };
271        // InvalidMetadata has no source
272        assert!(err.source().is_none());
273    }
274}