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(
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#[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}