1use crate::{Edge, Result, TrailgenError, TurnBan, Vertex, WalkGraph};
2use serde::{Deserialize, Serialize as _};
3
4pub const GRAPH_CACHE: &str = "cache/graph.bin";
5const MAGIC: &[u8; 16] = b"TRAILGEN-GRAPH\0\0";
6const FORMAT: u16 = 2;
7
8pub fn encode_graph(graph: &WalkGraph) -> Result<Vec<u8>> {
9 let mut bytes = Vec::new();
10 bytes.extend_from_slice(MAGIC);
11 bytes.extend_from_slice(&FORMAT.to_le_bytes());
12 let mut encoder = zstd::stream::write::Encoder::new(bytes, 3)
13 .map_err(|error| TrailgenError::InvalidData(format!("open graph encoder: {error}")))?;
14 graph
15 .serialize(&mut rmp_serde::Serializer::new(&mut encoder).with_struct_map())
16 .map_err(|error| TrailgenError::InvalidData(format!("encode graph cache: {error}")))?;
17 let bytes = encoder
18 .finish()
19 .map_err(|error| TrailgenError::InvalidData(format!("finish graph cache: {error}")))?;
20 Ok(bytes)
21}
22
23pub fn decode_graph(bytes: &[u8]) -> Result<WalkGraph> {
24 let Some((header, body)) = bytes.split_at_checked(MAGIC.len() + size_of::<u16>()) else {
25 return Err(TrailgenError::InvalidData(
26 "graph cache header is truncated".to_owned(),
27 ));
28 };
29 if &header[..MAGIC.len()] != MAGIC {
30 return Err(TrailgenError::InvalidData(
31 "graph cache has the wrong format signature".to_owned(),
32 ));
33 }
34 let format = u16::from_le_bytes(
35 header[MAGIC.len()..]
36 .try_into()
37 .expect("header length was checked"),
38 );
39 if format != FORMAT {
40 return Err(TrailgenError::InvalidData(format!(
41 "graph cache format {format} is unsupported"
42 )));
43 }
44 let decoder = zstd::stream::read::Decoder::new(body)
45 .map_err(|error| TrailgenError::InvalidData(format!("open graph cache: {error}")))?;
46 let stored = rmp_serde::from_read::<_, CachedGraph>(decoder)
47 .map_err(|error| TrailgenError::InvalidData(format!("decode graph cache: {error}")))?;
48 let mut graph = WalkGraph {
49 vertices: stored.vertices,
50 edges: stored.edges,
51 turn_bans: stored.turn_bans,
52 adjacency: Vec::new(),
53 };
54 graph.validate()?;
55 graph.rebuild_adjacency();
56 Ok(graph)
57}
58
59#[derive(Deserialize)]
60struct CachedGraph {
61 vertices: Vec<Vertex>,
62 edges: Vec<Edge>,
63 #[serde(default)]
64 turn_bans: Vec<TurnBan>,
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70 use crate::{GraphBuilder, io::geojson};
71
72 #[test]
73 fn graph_cache_is_compact_exact_and_self_identifying() {
74 let graph = GraphBuilder::default()
75 .build(
76 &geojson::network_from_str(include_str!("../tests/fixtures/mini_network.geojson"))
77 .unwrap(),
78 )
79 .unwrap();
80 let encoded = encode_graph(&graph).unwrap();
81
82 assert_eq!(decode_graph(&encoded).unwrap(), graph);
83 assert!(encoded.len() < serde_json::to_vec(&graph).unwrap().len());
84 let mut corrupt = encoded;
85 corrupt[0] ^= 0xff;
86 assert!(decode_graph(&corrupt).is_err());
87 }
88}