1use std::io;
2#[cfg(feature = "manage")]
3use std::path::Path;
4use std::path::PathBuf;
5#[cfg(feature = "manage")]
6use std::time::{Duration, SystemTime};
7
8#[cfg(feature = "manage")]
9use crate::BuildId;
10
11pub type Result<T> = std::result::Result<T, Error>;
13
14#[derive(Debug, thiserror::Error)]
16#[non_exhaustive]
17pub enum Error {
18 #[error("failed to {operation} at {path}: {source}")]
20 Io {
21 operation: &'static str,
23 path: PathBuf,
25 #[source]
27 source: io::Error,
28 },
29
30 #[cfg(feature = "lookup")]
32 #[error("cache directory is not secure: {path}")]
33 InsecureDirectory {
34 path: PathBuf,
36 },
37
38 #[cfg(feature = "lookup")]
40 #[error("cache entry is not a trusted regular file: {path}")]
41 UntrustedEntry {
42 path: PathBuf,
44 },
45
46 #[cfg(feature = "lookup")]
48 #[error("XDG cache home is unavailable")]
49 CacheHomeUnavailable,
50
51 #[cfg(feature = "lookup")]
53 #[error("invalid XDG cache application identifier: {value}")]
54 InvalidApplicationId {
55 value: PathBuf,
57 },
58
59 #[cfg(feature = "manage")]
61 #[error(transparent)]
62 InvalidGsym(Box<InvalidGsymError>),
63
64 #[cfg(feature = "manage")]
66 #[error(transparent)]
67 BuildIdMismatch(Box<BuildIdMismatchError>),
68
69 #[cfg(feature = "manage")]
71 #[error(
72 "negative-cache lifetime {lifetime:?} must be greater than zero and at most {maximum:?}"
73 )]
74 InvalidFailureTtl {
75 lifetime: Duration,
77 maximum: Duration,
79 },
80
81 #[cfg(feature = "manage")]
83 #[error("negative-cache expiration cannot be represented from {now:?} with {lifetime:?}")]
84 FailureExpirationUnrepresentable {
85 now: SystemTime,
87 lifetime: Duration,
89 },
90}
91
92#[cfg(feature = "manage")]
94#[derive(Debug, thiserror::Error)]
95#[error("invalid GSYM file at {}: {source}", path.display())]
96pub struct InvalidGsymError {
97 path: PathBuf,
98 source: gsym::Error,
99}
100
101#[cfg(feature = "manage")]
102impl InvalidGsymError {
103 pub(crate) fn new(path: &Path, source: gsym::Error) -> Self {
104 Self {
105 path: path.to_path_buf(),
106 source,
107 }
108 }
109
110 #[must_use]
112 pub fn path(&self) -> &Path {
113 &self.path
114 }
115
116 #[must_use]
118 pub const fn gsym_error(&self) -> &gsym::Error {
119 &self.source
120 }
121}
122
123#[cfg(feature = "manage")]
124const BUILD_ID_PREFIX_LEN: usize = 32;
125
126#[cfg(feature = "manage")]
128#[derive(Debug, thiserror::Error)]
129#[error(
130 "GSYM build identifier prefix {} ({actual_len} bytes) at {} does not match cache key {expected}",
131 HexBytes(actual_prefix, actual_len),
132 path.display()
133)]
134pub struct BuildIdMismatchError {
135 path: PathBuf,
136 expected: BuildId,
137 actual_len: usize,
138 actual_prefix: [u8; BUILD_ID_PREFIX_LEN],
139}
140
141#[cfg(feature = "manage")]
142impl BuildIdMismatchError {
143 pub(crate) fn new(path: &Path, expected: BuildId, actual: &[u8]) -> Self {
144 let mut actual_prefix = [0; BUILD_ID_PREFIX_LEN];
145 let prefix_len = actual.len().min(BUILD_ID_PREFIX_LEN);
146 if let (Some(destination), Some(source)) = (
147 actual_prefix.get_mut(..prefix_len),
148 actual.get(..prefix_len),
149 ) {
150 destination.copy_from_slice(source);
151 }
152 Self {
153 path: path.to_path_buf(),
154 expected,
155 actual_len: actual.len(),
156 actual_prefix,
157 }
158 }
159
160 #[must_use]
162 pub fn path(&self) -> &Path {
163 &self.path
164 }
165
166 #[must_use]
168 pub const fn expected(&self) -> &BuildId {
169 &self.expected
170 }
171
172 #[must_use]
174 pub const fn actual_len(&self) -> usize {
175 self.actual_len
176 }
177
178 #[must_use]
180 pub fn actual_prefix(&self) -> &[u8] {
181 self.actual_prefix
182 .get(..self.actual_len.min(BUILD_ID_PREFIX_LEN))
183 .unwrap_or_default()
184 }
185}
186
187#[cfg(feature = "manage")]
188struct HexBytes<'bytes>(&'bytes [u8; BUILD_ID_PREFIX_LEN], &'bytes usize);
189
190#[cfg(feature = "manage")]
191impl std::fmt::Display for HexBytes<'_> {
192 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
193 for byte in self
194 .0
195 .get(..(*self.1).min(BUILD_ID_PREFIX_LEN))
196 .unwrap_or_default()
197 {
198 write!(formatter, "{byte:02x}")?;
199 }
200 Ok(())
201 }
202}
203
204#[cfg(all(test, feature = "manage"))]
205mod tests {
206 use super::Error;
207 use std::mem::size_of;
208
209 #[test]
210 fn cold_diagnostics_do_not_inflate_the_cache_result() {
211 assert!(size_of::<Error>() <= 48);
212 }
213}
214
215#[cfg(feature = "lookup")]
216pub(crate) fn io_error(
217 operation: &'static str,
218 path: impl Into<PathBuf>,
219 source: io::Error,
220) -> Error {
221 Error::Io {
222 operation,
223 path: path.into(),
224 source,
225 }
226}