1use std::path::Path;
4
5pub type Result<T> = std::result::Result<T, Error>;
6
7#[derive(Debug, thiserror::Error)]
8pub enum Error {
9 #[error("not a keychain database: the file does not start with \"kych\"")]
10 NotAKeychain,
11
12 #[error("keychain is truncated: wanted {length} bytes at offset {offset}")]
13 Truncated { offset: usize, length: usize },
14
15 #[error("malformed keychain: {0}")]
16 Format(String),
17
18 #[error("keychain has no {0} table")]
19 MissingTable(&'static str),
20
21 #[error("incorrect password, or this keychain was not encrypted with one")]
22 WrongPassword,
23
24 #[error("the keychain is locked; unlock it with a password first")]
25 Locked,
26
27 #[error("no key in this keychain can decrypt that item (label {label})")]
29 MissingItemKey { label: String },
30
31 #[error("{0}")]
32 Crypto(&'static str),
33
34 #[error("no item matched")]
35 NoSuchItem,
36
37 #[error("an item with those attributes already exists")]
38 DuplicateItem,
39
40 #[error("{context}: {source}")]
41 Io {
42 context: String,
43 #[source]
44 source: std::io::Error,
45 },
46
47 #[error("{0}")]
48 Other(String),
49}
50
51impl Error {
52 pub fn truncated(offset: usize, length: usize) -> Self {
53 Self::Truncated { offset, length }
54 }
55
56 pub fn format(message: impl Into<String>) -> Self {
57 Self::Format(message.into())
58 }
59
60 pub fn other(message: impl Into<String>) -> Self {
61 Self::Other(message.into())
62 }
63
64 pub fn io(context: impl Into<String>, source: std::io::Error) -> Self {
65 Self::Io {
66 context: context.into(),
67 source,
68 }
69 }
70
71 pub fn reading(path: &Path, source: std::io::Error) -> Self {
72 Self::io(format!("could not read {}", path.display()), source)
73 }
74
75 pub fn exit_code(&self) -> u8 {
78 match self {
79 Self::NoSuchItem => 44,
80 Self::WrongPassword | Self::Locked => 45,
81 Self::DuplicateItem => 46,
82 _ => 1,
83 }
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[test]
92 fn exit_codes_separate_the_common_outcomes() {
93 assert_eq!(Error::NoSuchItem.exit_code(), 44);
94 assert_eq!(Error::WrongPassword.exit_code(), 45);
95 assert_eq!(Error::DuplicateItem.exit_code(), 46);
96 assert_eq!(Error::NotAKeychain.exit_code(), 1);
97 }
98
99 #[test]
100 fn messages_name_the_problem() {
101 assert!(Error::truncated(12, 4).to_string().contains("offset 12"));
102 assert!(Error::NotAKeychain.to_string().contains("kych"));
103 }
104}