solana_sdk/
deserialize_utils.rs

1use serde::{Deserialize, Deserializer};
2
3/// This helper function enables successful deserialization of versioned structs; new structs may
4/// include additional fields if they impl Default and are added to the end of the struct. Right
5/// now, this function is targeted at `bincode` deserialization; the error match may need to be
6/// updated if another package needs to be used in the future.
7pub fn default_on_eof<'de, T, D>(d: D) -> Result<T, D::Error>
8where
9    D: Deserializer<'de>,
10    T: Deserialize<'de> + Default,
11{
12    let result = T::deserialize(d);
13    ignore_eof_error::<'de, T, D::Error>(result)
14}
15
16pub fn ignore_eof_error<'de, T, D>(result: Result<T, D>) -> Result<T, D>
17where
18    T: Deserialize<'de> + Default,
19    D: std::fmt::Display,
20{
21    match result {
22        Err(err) if err.to_string() == "io error: unexpected end of file" => Ok(T::default()),
23        Err(err) if err.to_string() == "io error: failed to fill whole buffer" => Ok(T::default()),
24        result => result,
25    }
26}
27
28#[cfg(test)]
29pub mod tests {
30    use {super::*, bincode::deserialize};
31
32    #[test]
33    fn test_default_on_eof() {
34        #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
35        struct Foo {
36            bar: u16,
37            #[serde(deserialize_with = "default_on_eof")]
38            baz: Option<u16>,
39            #[serde(deserialize_with = "default_on_eof")]
40            quz: String,
41        }
42
43        let data = vec![1, 0];
44        assert_eq!(
45            Foo {
46                bar: 1,
47                baz: None,
48                quz: "".to_string(),
49            },
50            deserialize(&data).unwrap()
51        );
52
53        let data = vec![1, 0, 0];
54        assert_eq!(
55            Foo {
56                bar: 1,
57                baz: None,
58                quz: "".to_string(),
59            },
60            deserialize(&data).unwrap()
61        );
62
63        let data = vec![1, 0, 1];
64        assert_eq!(
65            Foo {
66                bar: 1,
67                baz: None,
68                quz: "".to_string(),
69            },
70            deserialize(&data).unwrap()
71        );
72
73        let data = vec![1, 0, 1, 0];
74        assert_eq!(
75            Foo {
76                bar: 1,
77                baz: None,
78                quz: "".to_string(),
79            },
80            deserialize(&data).unwrap()
81        );
82
83        let data = vec![1, 0, 1, 0, 0, 1];
84        assert_eq!(
85            Foo {
86                bar: 1,
87                baz: Some(0),
88                quz: "".to_string(),
89            },
90            deserialize(&data).unwrap()
91        );
92
93        let data = vec![1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 116];
94        assert_eq!(
95            Foo {
96                bar: 1,
97                baz: Some(0),
98                quz: "t".to_string(),
99            },
100            deserialize(&data).unwrap()
101        );
102    }
103
104    #[test]
105    #[should_panic]
106    fn test_default_on_eof_additional_untagged_fields() {
107        // If later fields are not tagged `deserialize_with = "default_on_eof"`, deserialization
108        // will panic on any missing fields/data
109        #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
110        struct Foo {
111            bar: u16,
112            #[serde(deserialize_with = "default_on_eof")]
113            baz: Option<u16>,
114            quz: String,
115        }
116
117        // Fully populated struct will deserialize
118        let data = vec![1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 116];
119        assert_eq!(
120            Foo {
121                bar: 1,
122                baz: Some(0),
123                quz: "t".to_string(),
124            },
125            deserialize(&data).unwrap()
126        );
127
128        // Will panic because `quz` is missing, even though `baz` is tagged
129        let data = vec![1, 0, 1, 0];
130        assert_eq!(
131            Foo {
132                bar: 1,
133                baz: None,
134                quz: "".to_string(),
135            },
136            deserialize(&data).unwrap()
137        );
138    }
139}