Skip to main content

rakata_formats/vis/
reader.rs

1//! VIS ASCII reader.
2
3use std::io::Read;
4
5use rakata_core::{decode_text_strict, TextEncoding};
6
7use super::{canonical_room, Vis, VisError};
8
9/// Reads a VIS graph from a reader.
10///
11/// # Errors
12///
13/// [`VisError::Io`] when the stream will not read to end, and whatever
14/// [`read_vis_from_bytes`] reports for the bytes it collected.
15#[cfg_attr(
16    feature = "tracing",
17    tracing::instrument(level = "debug", skip(reader))
18)]
19pub fn read_vis<R: Read>(reader: &mut R) -> Result<Vis, VisError> {
20    let mut bytes = Vec::new();
21    reader.read_to_end(&mut bytes)?;
22    read_vis_from_bytes(&bytes)
23}
24
25/// Reads a VIS graph from bytes.
26///
27/// # Errors
28///
29/// [`VisError::TextDecoding`] when `bytes` are not Windows-1252 text, and
30/// [`VisError::InvalidData`] when a line is not the room-and-count grammar or
31/// a room's child list runs off the end. VIS carries no header, so bytes that
32/// are not a VIS fail on their first line rather than on a signature.
33#[cfg_attr(
34    feature = "tracing",
35    tracing::instrument(level = "debug", skip(bytes), fields(bytes_len = bytes.len()))
36)]
37pub fn read_vis_from_bytes(bytes: &[u8]) -> Result<Vis, VisError> {
38    let text = decode_text_strict(bytes, TextEncoding::Windows1252).map_err(|source| {
39        VisError::TextDecoding {
40            context: "VIS payload".into(),
41            source,
42        }
43    })?;
44    let lines: Vec<&str> = text.lines().collect();
45
46    let mut vis = Vis::new();
47    let mut pairs: Vec<(String, String)> = Vec::new();
48
49    let mut line_index = 0usize;
50    while line_index < lines.len() {
51        let line = lines[line_index];
52        let tokens: Vec<&str> = line.split_whitespace().collect();
53        if tokens.is_empty() {
54            line_index += 1;
55            continue;
56        }
57
58        if tokens.len() >= 2 && tokens[1].starts_with('V') {
59            line_index += 1;
60            continue;
61        }
62
63        let observer = canonical_room(tokens[0]);
64        let count_token = tokens.get(1).ok_or_else(|| {
65            VisError::InvalidData(format!(
66                "line {} missing child count for room `{observer}`",
67                line_index + 1
68            ))
69        })?;
70        let child_count = count_token.parse::<usize>().map_err(|_| {
71            VisError::InvalidData(format!(
72                "line {} has invalid child count `{count_token}` for room `{observer}`",
73                line_index + 1
74            ))
75        })?;
76
77        vis.add_room(&observer);
78        line_index += 1;
79
80        for child_index in 0..child_count {
81            let child_line = lines.get(line_index).ok_or_else(|| {
82                VisError::InvalidData(format!(
83                    "missing child line {} for observer `{observer}`",
84                    child_index + 1
85                ))
86            })?;
87            let child_tokens: Vec<&str> = child_line.split_whitespace().collect();
88            if child_tokens.is_empty() {
89                return Err(VisError::InvalidData(format!(
90                    "line {} missing child room name for observer `{observer}`",
91                    line_index + 1
92                )));
93            }
94            let child = canonical_room(child_tokens[0]);
95            pairs.push((observer.clone(), child));
96            line_index += 1;
97        }
98    }
99
100    for (observer, child) in pairs {
101        vis.add_room(&observer);
102        vis.add_room(&child);
103        vis.set_visible(&observer, &child, true)?;
104    }
105
106    Ok(vis)
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112    use crate::vis::write_vis_to_vec;
113
114    const TEST_VIS: &[u8] = include_bytes!(concat!(
115        env!("CARGO_MANIFEST_DIR"),
116        "/../../fixtures/test.vis"
117    ));
118    const CORRUPTED_VIS: &[u8] = include_bytes!(concat!(
119        env!("CARGO_MANIFEST_DIR"),
120        "/../../fixtures/test_corrupted.vis"
121    ));
122
123    #[test]
124    fn parses_vis_fixture() {
125        let vis = read_vis_from_bytes(TEST_VIS).expect("fixture should parse");
126
127        assert!(vis.get_visible("room_01", "room_02").expect("room exists"));
128        assert!(vis.get_visible("room_01", "room_03").expect("room exists"));
129        assert!(vis.get_visible("room_01", "room_04").expect("room exists"));
130
131        assert!(vis.get_visible("room_02", "room_01").expect("room exists"));
132        assert!(!vis.get_visible("room_02", "room_03").expect("room exists"));
133        assert!(!vis.get_visible("room_02", "room_04").expect("room exists"));
134
135        assert!(vis.get_visible("room_03", "room_01").expect("room exists"));
136        assert!(vis.get_visible("room_03", "room_04").expect("room exists"));
137        assert!(!vis.get_visible("room_03", "room_02").expect("room exists"));
138
139        assert!(vis.get_visible("room_04", "room_01").expect("room exists"));
140        assert!(vis.get_visible("room_04", "room_03").expect("room exists"));
141        assert!(!vis.get_visible("room_04", "room_02").expect("room exists"));
142    }
143
144    #[test]
145    fn roundtrip_synthetic_vis() {
146        let mut vis = Vis::new();
147        vis.add_room("room_a");
148        vis.add_room("room_b");
149        vis.add_room("room_c");
150        vis.set_visible("room_a", "room_b", true)
151            .expect("rooms exist");
152        vis.set_visible("room_a", "room_c", true)
153            .expect("rooms exist");
154        vis.set_visible("room_b", "room_a", true)
155            .expect("rooms exist");
156
157        let bytes = write_vis_to_vec(&vis).expect("write should succeed");
158        let parsed = read_vis_from_bytes(&bytes).expect("read should succeed");
159        assert_eq!(parsed, vis);
160    }
161
162    #[test]
163    fn writer_is_deterministic_for_synthetic_vis() {
164        let mut vis = Vis::new();
165        vis.add_room("room_a");
166        vis.add_room("room_b");
167        vis.set_visible("room_a", "room_b", true)
168            .expect("rooms exist");
169
170        let first = write_vis_to_vec(&vis).expect("first write should succeed");
171        let second = write_vis_to_vec(&vis).expect("second write should succeed");
172        assert_eq!(first, second);
173    }
174
175    #[test]
176    fn read_write_roundtrip_preserves_fixture_semantics() {
177        let parsed = read_vis_from_bytes(TEST_VIS).expect("fixture should parse");
178        let bytes = write_vis_to_vec(&parsed).expect("write should succeed");
179        let reparsed = read_vis_from_bytes(&bytes).expect("re-read should succeed");
180        assert_eq!(reparsed, parsed);
181    }
182
183    #[test]
184    fn rejects_corrupted_vis_fixture() {
185        let err = read_vis_from_bytes(CORRUPTED_VIS).expect_err("must fail");
186        assert!(matches!(err, VisError::InvalidData(_)));
187    }
188
189    #[test]
190    fn rejects_truncated_child_list() {
191        let bytes = b"room_01 1\n";
192        let err = read_vis_from_bytes(bytes).expect_err("must fail");
193        assert!(matches!(err, VisError::InvalidData(_)));
194    }
195
196    #[test]
197    fn parser_skips_version_header_lines() {
198        let bytes = b"room_01 V3.28\nroom_01 1\n  room_02\n";
199        let vis = read_vis_from_bytes(bytes).expect("should parse");
200        assert!(vis.room_exists("room_01"));
201        assert!(vis.room_exists("room_02"));
202        assert!(vis.get_visible("room_01", "room_02").expect("rooms exist"));
203    }
204
205    #[test]
206    fn set_visible_rejects_unknown_rooms() {
207        let mut vis = Vis::new();
208        vis.add_room("room_01");
209        let err = vis
210            .set_visible("room_01", "room_02", true)
211            .expect_err("must fail");
212        assert!(matches!(err, VisError::MissingRoom(_)));
213    }
214
215    #[test]
216    fn parser_accepts_windows_1252_non_utf8_bytes() {
217        let bytes = b"sall\xe9_01 1\n  room_02\n";
218        let vis = read_vis_from_bytes(bytes).expect("must parse");
219        assert!(vis.room_exists("sall\u{e9}_01"));
220        assert!(vis.room_exists("room_02"));
221        assert!(vis
222            .get_visible("sall\u{e9}_01", "room_02")
223            .expect("rooms exist"));
224    }
225
226    #[test]
227    fn writer_rejects_unencodable_text() {
228        let mut vis = Vis::new();
229        vis.add_room("room_01");
230        vis.add_room("room_\u{1f600}");
231        vis.set_visible("room_01", "room_\u{1f600}", true)
232            .expect("rooms exist");
233        let err = write_vis_to_vec(&vis).expect_err("must fail");
234        assert!(matches!(err, VisError::TextEncoding { .. }));
235    }
236}