rakata_formats/vis/
reader.rs1use std::io::Read;
4
5use rakata_core::{decode_text_strict, TextEncoding};
6
7use super::{canonical_room, Vis, VisError};
8
9#[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#[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}