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