Skip to main content

gsym_cache/
error.rs

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
11/// Result type used by this crate.
12pub type Result<T> = std::result::Result<T, Error>;
13
14/// Cache operation failure.
15#[derive(Debug, thiserror::Error)]
16#[non_exhaustive]
17pub enum Error {
18    /// A filesystem operation failed.
19    #[error("failed to {operation} at {path}: {source}")]
20    Io {
21        /// Operation that failed.
22        operation: &'static str,
23        /// Affected path.
24        path: PathBuf,
25        /// Underlying operating-system error.
26        #[source]
27        source: io::Error,
28    },
29
30    /// A cache directory is missing, untrusted, or unsafe to traverse.
31    #[cfg(feature = "lookup")]
32    #[error("cache directory is not secure: {path}")]
33    InsecureDirectory {
34        /// Rejected directory.
35        path: PathBuf,
36    },
37
38    /// A cache entry is not an owned regular file.
39    #[cfg(feature = "lookup")]
40    #[error("cache entry is not a trusted regular file: {path}")]
41    UntrustedEntry {
42        /// Rejected entry.
43        path: PathBuf,
44    },
45
46    /// No absolute XDG cache directory or home directory is available.
47    #[cfg(feature = "lookup")]
48    #[error("XDG cache home is unavailable")]
49    CacheHomeUnavailable,
50
51    /// An XDG application identifier is not one normal path component.
52    #[cfg(feature = "lookup")]
53    #[error("invalid XDG cache application identifier: {value}")]
54    InvalidApplicationId {
55        /// Rejected identifier.
56        value: PathBuf,
57    },
58
59    /// A cache file is not valid GSYM.
60    #[cfg(feature = "manage")]
61    #[error(transparent)]
62    InvalidGsym(Box<InvalidGsymError>),
63
64    /// The GSYM build identifier differs from its cache key.
65    #[cfg(feature = "manage")]
66    #[error(transparent)]
67    BuildIdMismatch(Box<BuildIdMismatchError>),
68
69    /// A negative-cache lifetime is zero or exceeds the maximum.
70    #[cfg(feature = "manage")]
71    #[error(
72        "negative-cache lifetime {lifetime:?} must be greater than zero and at most {maximum:?}"
73    )]
74    InvalidFailureTtl {
75        /// Rejected cache lifetime.
76        lifetime: Duration,
77        /// Maximum accepted lifetime.
78        maximum: Duration,
79    },
80
81    /// A negative-cache expiration cannot be represented by the system clock.
82    #[cfg(feature = "manage")]
83    #[error("negative-cache expiration cannot be represented from {now:?} with {lifetime:?}")]
84    FailureExpirationUnrepresentable {
85        /// Clock value used to calculate the expiration.
86        now: SystemTime,
87        /// Requested cache lifetime.
88        lifetime: Duration,
89    },
90}
91
92/// Invalid GSYM file with its cache path.
93#[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    /// Returns the rejected file's path.
111    #[must_use]
112    pub fn path(&self) -> &Path {
113        &self.path
114    }
115
116    /// Returns the GSYM parsing or verification error.
117    #[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/// GSYM build-identifier mismatch with bounded diagnostic data.
127#[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    /// Returns the rejected file's path.
161    #[must_use]
162    pub fn path(&self) -> &Path {
163        &self.path
164    }
165
166    /// Returns the requested cache key.
167    #[must_use]
168    pub const fn expected(&self) -> &BuildId {
169        &self.expected
170    }
171
172    /// Returns the total GSYM build-identifier length.
173    #[must_use]
174    pub const fn actual_len(&self) -> usize {
175        self.actual_len
176    }
177
178    /// Returns at most the first 32 bytes of the GSYM build identifier.
179    #[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}