1use std::path::Path;
13
14use crate::Error;
15
16pub fn read_rds_file(path: impl AsRef<Path>) -> Result<rd_rds::RObject, Error> {
19 let path = path.as_ref();
20 rd_rds::file::read(path).map_err(|error| map_file_error(path, error))
21}
22
23pub(crate) fn map_file_error(path: &Path, error: rd_rds::file::ReadError) -> Error {
24 match error {
25 rd_rds::file::ReadError::Io { path, source } => Error::io(path, source),
26 rd_rds::file::ReadError::UnknownEnvelope { magic } => Error::UnsupportedCompression {
27 path: path.to_path_buf(),
28 magic,
29 },
30 rd_rds::file::ReadError::Decode(error) => Error::Rds(error),
31 error => Error::RdsFile(error),
32 }
33}
34
35pub fn decode_rdb_record(bytes: &[u8]) -> Result<rd_rds::RObject, Error> {
41 let decompressed = rd_rds::lazyload::decode_stored_record(
42 bytes,
43 rd_rds::lazyload::Compression::Zlib,
44 rd_rds::lazyload::Options::default(),
45 )
46 .map_err(|error| map_record_decode_error(error, bytes.len()))?;
47 Ok(rd_rds::parse(&decompressed)?)
48}
49
50fn map_record_decode_error(error: rd_rds::lazyload::Error, stored_len: usize) -> Error {
51 match error {
52 rd_rds::lazyload::Error::StoredRecordSizeLimitExceeded { limit } => {
53 Error::StoredRecordSizeLimitExceeded { limit }
54 }
55 rd_rds::lazyload::Error::DecompressedRecordSizeLimitExceeded { limit } => {
56 Error::DecompressedRecordSizeLimitExceeded { limit }
57 }
58 rd_rds::lazyload::Error::RecordLengthPrefixMissing => Error::RecordSizeMismatch {
59 expected: 4,
60 actual: stored_len,
61 },
62 rd_rds::lazyload::Error::RecordSizeMismatch { declared, actual } => {
63 Error::RecordSizeMismatch {
64 expected: declared,
65 actual,
66 }
67 }
68 rd_rds::lazyload::Error::CompressionUnsupported { compression } => {
69 Error::UnsupportedRecordCompression { compression }
70 }
71 other => {
72 Error::MalformedIndex(format!("zlib decompression of .rdb record failed: {other}"))
73 }
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80
81 #[test]
82 fn maps_unknown_envelope_to_existing_error() {
83 let path = std::env::temp_dir().join(format!(
84 "rd-helpdb-unknown-envelope-{}.rds",
85 std::process::id()
86 ));
87 std::fs::write(&path, b"A\nunknown").expect("write unknown envelope");
88 let err = read_rds_file(&path).unwrap_err();
89 let _ = std::fs::remove_file(&path);
90 assert!(matches!(
91 err,
92 Error::UnsupportedCompression { magic, .. } if magic == b"A\nunkn"
93 ));
94 }
95
96 #[test]
97 fn rejects_short_records() {
98 let err = decode_rdb_record(&[0, 1, 2]).unwrap_err();
99 assert!(matches!(
100 err,
101 Error::RecordSizeMismatch {
102 expected: 4,
103 actual: 3
104 }
105 ));
106 }
107
108 #[test]
109 fn compatibility_record_decoder_rejects_trailing_bytes() {
110 let mut bytes = include_bytes!("../tests/fixtures/data/rd_minimal_v3.rdbentry").to_vec();
111 bytes.push(0);
112 let err = decode_rdb_record(&bytes).unwrap_err();
113 assert!(matches!(err, Error::MalformedIndex(message) if message.contains("trailing")));
114 }
115
116 #[test]
117 fn compatibility_record_decoder_maps_declared_size_mismatch() {
118 let mut bytes = include_bytes!("../tests/fixtures/data/rd_minimal_v3.rdbentry").to_vec();
119 bytes[3] = bytes[3].wrapping_add(1);
120 let err = decode_rdb_record(&bytes).unwrap_err();
121 assert!(matches!(err, Error::RecordSizeMismatch { .. }));
122 }
123}