Skip to main content

trailgen_core/
cache.rs

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}