Skip to main content

voronoi_go/
serde_support.rs

1//! Serde implementations for the types whose wire shape is not the derived one.
2//!
3//! `docs/protocol.md` is the contract: a point is `[x, y]`, and a per-colour
4//! pair is `{"black": …, "white": …}`. Everything else derives.
5
6use serde::ser::{SerializeStruct, SerializeTuple};
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8
9use crate::{PerColor, Point};
10
11impl Serialize for Point {
12    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
13        let mut pair = serializer.serialize_tuple(2)?;
14        pair.serialize_element(&self.x)?;
15        pair.serialize_element(&self.y)?;
16        pair.end()
17    }
18}
19
20impl<'de> Deserialize<'de> for Point {
21    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
22        let [x, y] = <[f64; 2]>::deserialize(deserializer)?;
23        Ok(Self::new(x, y))
24    }
25}
26
27impl<T: Serialize> Serialize for PerColor<T> {
28    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
29        let mut pair = serializer.serialize_struct("PerColor", 2)?;
30        pair.serialize_field("black", self.black())?;
31        pair.serialize_field("white", self.white())?;
32        pair.end()
33    }
34}
35
36impl<'de, T: Deserialize<'de>> Deserialize<'de> for PerColor<T> {
37    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
38        #[derive(Deserialize)]
39        struct Pair<T> {
40            black: T,
41            white: T,
42        }
43
44        let Pair { black, white } = Pair::deserialize(deserializer)?;
45        Ok(Self::new(black, white))
46    }
47}
48
49#[cfg(test)]
50mod tests {
51    #![allow(clippy::unwrap_used, clippy::expect_used)]
52
53    use crate::{Color, GameDelta, PerColor, Point, Stone, StoneId};
54
55    #[test]
56    fn a_point_is_a_two_element_array() {
57        let json = serde_json::to_string(&Point::new(4.0, -1.5)).unwrap();
58        assert_eq!(json, "[4.0,-1.5]");
59        let back: Point = serde_json::from_str(&json).unwrap();
60        assert_eq!(back, Point::new(4.0, -1.5));
61    }
62
63    #[test]
64    fn a_color_is_a_lowercase_string() {
65        assert_eq!(serde_json::to_string(&Color::Black).unwrap(), "\"black\"");
66        assert_eq!(serde_json::to_string(&Color::White).unwrap(), "\"white\"");
67        let back: Color = serde_json::from_str("\"white\"").unwrap();
68        assert_eq!(back, Color::White);
69    }
70
71    #[test]
72    fn a_stone_matches_the_protocol_shape() {
73        let stone = Stone::new(StoneId::new(3), Color::Black, Point::new(4.0, 4.0));
74        let json = serde_json::to_string(&stone).unwrap();
75        assert_eq!(json, r#"{"id":3,"color":"black","pos":[4.0,4.0]}"#);
76        assert_eq!(serde_json::from_str::<Stone>(&json).unwrap(), stone);
77    }
78
79    #[test]
80    fn a_delta_matches_the_protocol_shape() {
81        let stone = Stone::new(StoneId::new(3), Color::Black, Point::new(4.0, 4.0));
82        let delta = GameDelta::capture(stone, vec![StoneId::new(1), StoneId::new(7)]);
83        let json = serde_json::to_string(&delta).unwrap();
84        assert_eq!(
85            json,
86            r#"{"newStone":{"id":3,"color":"black","pos":[4.0,4.0]},"capturedStoneIds":[1,7]}"#
87        );
88        assert_eq!(serde_json::from_str::<GameDelta>(&json).unwrap(), delta);
89    }
90
91    #[test]
92    fn a_pass_delta_has_a_null_stone() {
93        let json = serde_json::to_string(&GameDelta::pass()).unwrap();
94        assert_eq!(json, r#"{"newStone":null,"capturedStoneIds":[]}"#);
95    }
96
97    #[test]
98    fn a_per_color_pair_is_keyed_by_colour_name() {
99        let counts = PerColor::new(3_u32, 1);
100        let json = serde_json::to_string(&counts).unwrap();
101        assert_eq!(json, r#"{"black":3,"white":1}"#);
102        assert_eq!(
103            serde_json::from_str::<PerColor<u32>>(&json).unwrap(),
104            counts
105        );
106    }
107}