1mod decode;
39mod encode;
40mod vlq;
41
42pub use decode::decode;
43pub use encode::encode;
44#[cfg(feature = "parallel")]
45pub use encode::encode_parallel;
46pub use vlq::{vlq_decode, vlq_encode};
47
48use std::fmt;
49
50pub type Segment = Vec<i64>;
57
58pub type Line = Vec<Segment>;
60
61pub type SourceMapMappings = Vec<Line>;
63
64#[derive(Debug, Clone, PartialEq, Eq)]
66pub enum DecodeError {
67 InvalidBase64 { byte: u8, offset: usize },
69 UnexpectedEof { offset: usize },
71 VlqOverflow { offset: usize },
73}
74
75impl fmt::Display for DecodeError {
76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77 match self {
78 Self::InvalidBase64 { byte, offset } => {
79 write!(
80 f,
81 "invalid base64 character 0x{byte:02x} at offset {offset}"
82 )
83 }
84 Self::UnexpectedEof { offset } => {
85 write!(f, "unexpected end of input at offset {offset}")
86 }
87 Self::VlqOverflow { offset } => {
88 write!(f, "VLQ value overflow at offset {offset}")
89 }
90 }
91 }
92}
93
94impl std::error::Error for DecodeError {}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 #[test]
103 fn roundtrip_empty() {
104 let decoded = decode("").unwrap();
105 assert!(decoded.is_empty());
106 assert_eq!(encode(&decoded), "");
107 }
108
109 #[test]
110 fn roundtrip_simple() {
111 let input = "AAAA;AACA";
112 let decoded = decode(input).unwrap();
113 let encoded = encode(&decoded);
114 assert_eq!(encoded, input);
115 }
116
117 #[test]
118 fn roundtrip_multiple_segments() {
119 let input = "AAAA,GAAG,EAAE;AACA";
120 let decoded = decode(input).unwrap();
121 let encoded = encode(&decoded);
122 assert_eq!(encoded, input);
123 }
124
125 #[test]
126 fn roundtrip_large_values() {
127 let mappings = vec![vec![vec![1000_i64, 50, 999, 500, 100]]];
128 let encoded = encode(&mappings);
129 let decoded = decode(&encoded).unwrap();
130 assert_eq!(decoded, mappings);
131 }
132
133 #[test]
134 fn roundtrip_negative_deltas() {
135 let mappings = vec![vec![vec![10_i64, 0, 10, 10], vec![20, 0, 5, 5]]];
136 let encoded = encode(&mappings);
137 let decoded = decode(&encoded).unwrap();
138 assert_eq!(decoded, mappings);
139 }
140
141 #[test]
144 fn decode_single_field_segment() {
145 let decoded = decode("A").unwrap();
146 assert_eq!(decoded.len(), 1);
147 assert_eq!(decoded[0].len(), 1);
148 assert_eq!(decoded[0][0], vec![0]);
149 }
150
151 #[test]
152 fn decode_four_field_segment() {
153 let decoded = decode("AAAA").unwrap();
154 assert_eq!(decoded.len(), 1);
155 assert_eq!(decoded[0].len(), 1);
156 assert_eq!(decoded[0][0], vec![0, 0, 0, 0]);
157 }
158
159 #[test]
160 fn decode_five_field_segment() {
161 let decoded = decode("AAAAA").unwrap();
162 assert_eq!(decoded.len(), 1);
163 assert_eq!(decoded[0].len(), 1);
164 assert_eq!(decoded[0][0], vec![0, 0, 0, 0, 0]);
165 }
166
167 #[test]
168 fn decode_negative_values() {
169 let decoded = decode("DADD").unwrap();
170 assert_eq!(decoded[0][0], vec![-1, 0, -1, -1]);
171 }
172
173 #[test]
174 fn decode_multiple_lines() {
175 let decoded = decode("AAAA;AACA;AACA").unwrap();
176 assert_eq!(decoded.len(), 3);
177 }
178
179 #[test]
180 fn decode_empty_lines() {
181 let decoded = decode("AAAA;;;AACA").unwrap();
182 assert_eq!(decoded.len(), 4);
183 assert!(decoded[1].is_empty());
184 assert!(decoded[2].is_empty());
185 }
186
187 #[test]
188 fn decode_trailing_semicolon() {
189 let decoded = decode("AAAA;").unwrap();
191 assert_eq!(decoded.len(), 2);
192 assert_eq!(decoded[0].len(), 1);
193 assert!(decoded[1].is_empty());
194 }
195
196 #[test]
197 fn decode_only_semicolons() {
198 let decoded = decode(";;;").unwrap();
199 assert_eq!(decoded.len(), 4);
200 for line in &decoded {
201 assert!(line.is_empty());
202 }
203 }
204
205 #[test]
208 fn decode_invalid_ascii_char() {
209 let err = decode("AA!A").unwrap_err();
210 assert_eq!(
211 err,
212 DecodeError::InvalidBase64 {
213 byte: b'!',
214 offset: 2
215 }
216 );
217 }
218
219 #[test]
220 fn decode_non_ascii_byte() {
221 let err = decode("AAÀ").unwrap_err();
223 assert_eq!(
224 err,
225 DecodeError::InvalidBase64 {
226 byte: 0xC3,
227 offset: 2
228 }
229 );
230 }
231
232 #[test]
233 fn decode_truncated_vlq() {
234 let err = decode("g").unwrap_err();
236 assert_eq!(err, DecodeError::UnexpectedEof { offset: 1 });
237 }
238
239 #[test]
240 fn decode_vlq_overflow() {
241 let err = decode("gggggggggggggg").unwrap_err();
244 matches!(err, DecodeError::VlqOverflow { .. });
245 }
246
247 #[test]
248 fn decode_truncated_segment() {
249 let err = decode("AC").unwrap_err();
251 assert!(matches!(
252 err,
253 DecodeError::UnexpectedEof { .. } | DecodeError::InvalidBase64 { .. }
254 ));
255 }
256
257 #[test]
260 fn encode_empty_segments_no_dangling_comma() {
261 let mappings = vec![vec![vec![], vec![0, 0, 0, 0], vec![], vec![2, 0, 0, 1]]];
263 let encoded = encode(&mappings);
264 assert!(
265 !encoded.contains(",,"),
266 "should not contain dangling commas"
267 );
268 let expected = encode(&vec![vec![vec![0, 0, 0, 0], vec![2, 0, 0, 1]]]);
270 assert_eq!(encoded, expected);
271 }
272
273 #[test]
274 fn encode_all_empty_segments() {
275 let mappings = vec![vec![vec![], vec![], vec![]]];
276 let encoded = encode(&mappings);
277 assert_eq!(encoded, "");
278 }
279
280 #[cfg(feature = "parallel")]
283 mod parallel_tests {
284 use super::*;
285
286 fn build_large_mappings(lines: usize, segments_per_line: usize) -> SourceMapMappings {
287 let mut mappings = Vec::with_capacity(lines);
288 for line in 0..lines {
289 let mut line_segments = Vec::with_capacity(segments_per_line);
290 for seg in 0..segments_per_line {
291 line_segments.push(vec![
292 (seg * 10) as i64, (seg % 5) as i64, line as i64, (seg * 5) as i64, (seg % 3) as i64, ]);
298 }
299 mappings.push(line_segments);
300 }
301 mappings
302 }
303
304 #[test]
305 fn parallel_matches_sequential_large() {
306 let mappings = build_large_mappings(2000, 10);
307 let sequential = encode(&mappings);
308 let parallel = encode_parallel(&mappings);
309 assert_eq!(sequential, parallel);
310 }
311
312 #[test]
313 fn parallel_matches_sequential_with_empty_lines() {
314 let mut mappings = build_large_mappings(1500, 8);
315 for i in (0..mappings.len()).step_by(3) {
317 mappings[i] = Vec::new();
318 }
319 let sequential = encode(&mappings);
320 let parallel = encode_parallel(&mappings);
321 assert_eq!(sequential, parallel);
322 }
323
324 #[test]
325 fn parallel_matches_sequential_mixed_segments() {
326 let mut mappings: SourceMapMappings = Vec::with_capacity(2000);
327 for line in 0..2000 {
328 let mut line_segments = Vec::new();
329 for seg in 0..8 {
330 if seg % 4 == 0 {
331 line_segments.push(vec![(seg * 10) as i64]);
333 } else if seg % 4 == 3 {
334 line_segments.push(vec![
336 (seg * 10) as i64,
337 (seg % 3) as i64,
338 line as i64,
339 (seg * 5) as i64,
340 (seg % 2) as i64,
341 ]);
342 } else {
343 line_segments.push(vec![
345 (seg * 10) as i64,
346 (seg % 3) as i64,
347 line as i64,
348 (seg * 5) as i64,
349 ]);
350 }
351 }
352 mappings.push(line_segments);
353 }
354 let sequential = encode(&mappings);
355 let parallel = encode_parallel(&mappings);
356 assert_eq!(sequential, parallel);
357 }
358
359 #[test]
360 fn parallel_roundtrip() {
361 let mappings = build_large_mappings(2000, 10);
362 let encoded = encode_parallel(&mappings);
363 let decoded = decode(&encoded).unwrap();
364 assert_eq!(decoded, mappings);
365 }
366
367 #[test]
368 fn parallel_fallback_for_small_maps() {
369 let mappings = build_large_mappings(10, 5);
371 let sequential = encode(&mappings);
372 let parallel = encode_parallel(&mappings);
373 assert_eq!(sequential, parallel);
374 }
375 }
376}