source_lang/error.rs
1//! The error type returned when a source cannot be added to a map.
2
3use alloc::boxed::Box;
4use core::fmt;
5
6/// The reason a source could not be added to a [`SourceMap`](crate::SourceMap).
7///
8/// Adding a source can fail four ways, each a distinct, defined outcome rather
9/// than a panic or a silent corruption of the coordinate bookkeeping:
10///
11/// - the source is larger than the map's per-source ceiling
12/// ([`Oversize`](Self::Oversize)),
13/// - it does not fit in what remains of the shared 32-bit position space
14/// ([`SpaceExhausted`](Self::SpaceExhausted)),
15/// - its bytes are not valid UTF-8 ([`NotUtf8`](Self::NotUtf8)), or
16/// - the file behind a path could not be read ([`Io`](Self::Io), `std` only).
17///
18/// Every variant names the source it concerns so the failure is actionable when
19/// it is logged far from the call that produced it.
20///
21/// The enum is `#[non_exhaustive]`: a downstream `match` must include a wildcard
22/// arm, so later additions never force a breaking change on callers.
23///
24/// # Examples
25///
26/// ```
27/// use source_lang::{SourceMap, SourceMapError};
28///
29/// let mut map = SourceMap::new();
30/// // Raw bytes that are not valid UTF-8 are rejected, naming the source.
31/// let err = map.add_bytes("blob.bin", &[0xff, 0xfe]).unwrap_err();
32/// assert!(matches!(err, SourceMapError::NotUtf8 { .. }));
33/// ```
34#[derive(Clone, Debug, PartialEq, Eq)]
35#[non_exhaustive]
36pub enum SourceMapError {
37 /// The source is larger than the map's configured per-source ceiling.
38 ///
39 /// The ceiling defaults to `u32::MAX` — the addressing limit of the global
40 /// position space — and can be lowered with
41 /// [`SourceMap::set_max_source_len`](crate::SourceMap::set_max_source_len) to
42 /// bound how much a single untrusted input may load. For a file, the size is
43 /// checked against the path's metadata *before* the bytes are read, so an
44 /// oversize file is never pulled into memory.
45 Oversize {
46 /// Display name of the source that was rejected.
47 name: Box<str>,
48 /// Byte length of the source. For a file whose length is not known
49 /// before reading (a pipe, a virtual file, a file growing while read),
50 /// this is the number of bytes read before loading stopped (one past the
51 /// ceiling), a lower bound on the true length.
52 len: u64,
53 },
54
55 /// The source did not fit in what remained of the map's global space.
56 ///
57 /// Returned when the source is no larger than the per-source ceiling but
58 /// still exceeds the bytes left in the shared 32-bit position space — because
59 /// earlier sources have consumed the remainder — or when the map already
60 /// holds the maximum number of sources. The map is left unchanged, so the
61 /// caller may start a fresh map or split the input.
62 SpaceExhausted {
63 /// Byte length of the source that was rejected.
64 needed: u64,
65 /// Bytes of global position space that remained available.
66 available: u64,
67 },
68
69 /// The source's bytes are not valid UTF-8.
70 ///
71 /// A `SourceMap` stores text, so input from
72 /// [`add_bytes`](crate::SourceMap::add_bytes) or a file is validated before
73 /// it is stored. A truncated multi-byte sequence or stray binary byte is
74 /// reported here rather than stored as corrupt text.
75 NotUtf8 {
76 /// Display name of the source whose bytes failed validation.
77 name: Box<str>,
78 },
79
80 /// A file's contents could not be read from disk.
81 ///
82 /// Returned by [`add_file`](crate::SourceMap::add_file) when opening or
83 /// reading the path fails — a missing file, a directory, or a permission
84 /// error. The [`std::io::ErrorKind`] distinguishes the cause without carrying
85 /// a non-comparable [`std::io::Error`], so this variant stays `Clone` and
86 /// `Eq` like the rest.
87 #[cfg(feature = "std")]
88 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
89 Io {
90 /// Display name of the source (the path that was requested).
91 name: Box<str>,
92 /// The category of I/O failure.
93 kind: std::io::ErrorKind,
94 },
95}
96
97impl fmt::Display for SourceMapError {
98 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99 match self {
100 Self::Oversize { name, len } => write!(
101 f,
102 "source `{name}` of {len} bytes exceeds the maximum source length",
103 ),
104 Self::SpaceExhausted { needed, available } => write!(
105 f,
106 "source of {needed} bytes does not fit in the {available} bytes \
107 remaining in the global position space",
108 ),
109 Self::NotUtf8 { name } => {
110 write!(f, "source `{name}` is not valid UTF-8")
111 }
112 #[cfg(feature = "std")]
113 Self::Io { name, kind } => write!(f, "source `{name}` could not be read: {kind}"),
114 }
115 }
116}
117
118impl core::error::Error for SourceMapError {}
119
120#[cfg(test)]
121mod tests {
122 extern crate alloc;
123 use alloc::boxed::Box;
124 use alloc::string::ToString;
125
126 use super::*;
127
128 #[test]
129 fn test_space_exhausted_display_names_both_figures() {
130 let err = SourceMapError::SpaceExhausted {
131 needed: 10,
132 available: 4,
133 };
134 let text = err.to_string();
135 assert!(text.contains("10 bytes"), "{text}");
136 assert!(text.contains("4 bytes"), "{text}");
137 }
138
139 #[test]
140 fn test_oversize_display_names_source_and_length() {
141 let err = SourceMapError::Oversize {
142 name: Box::from("big.rs"),
143 len: 5_000_000_000,
144 };
145 let text = err.to_string();
146 assert!(text.contains("big.rs"), "{text}");
147 assert!(text.contains("5000000000"), "{text}");
148 }
149
150 #[test]
151 fn test_not_utf8_display_names_source() {
152 let err = SourceMapError::NotUtf8 {
153 name: Box::from("blob.bin"),
154 };
155 assert!(err.to_string().contains("blob.bin"));
156 }
157
158 #[cfg(feature = "std")]
159 #[test]
160 fn test_io_display_names_source_and_kind() {
161 let err = SourceMapError::Io {
162 name: Box::from("missing.rs"),
163 kind: std::io::ErrorKind::NotFound,
164 };
165 let text = err.to_string();
166 assert!(text.contains("missing.rs"), "{text}");
167 }
168
169 #[test]
170 fn test_error_is_clonable_and_equatable() {
171 let a = SourceMapError::SpaceExhausted {
172 needed: 1,
173 available: 0,
174 };
175 let b = a.clone();
176 assert_eq!(a, b);
177 }
178}