1use core::fmt;
9use core::marker::PhantomData;
10
11use serde::Deserialize;
12use serde::de::{self, Deserializer, IgnoredAny, MapAccess, Visitor};
13
14pub trait SaveFormat {
16 const NAME: &'static str;
19 const VERSION: u32;
22}
23
24pub struct SaveHeader<F> {
28 pub version: u32,
30 format: PhantomData<fn() -> F>,
31}
32
33impl<F: SaveFormat> SaveHeader<F> {
34 pub fn is_current(&self) -> bool {
37 self.version == F::VERSION
38 }
39}
40
41impl<F> Clone for SaveHeader<F> {
43 fn clone(&self) -> Self {
44 *self
45 }
46}
47
48impl<F> Copy for SaveHeader<F> {}
49
50impl<F> PartialEq for SaveHeader<F> {
51 fn eq(&self, other: &Self) -> bool {
52 self.version == other.version
53 }
54}
55
56impl<F> Eq for SaveHeader<F> {}
57
58impl<F> fmt::Debug for SaveHeader<F> {
59 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60 f.debug_struct("SaveHeader")
61 .field("version", &self.version)
62 .finish()
63 }
64}
65
66impl<'de, F: SaveFormat> Deserialize<'de> for SaveHeader<F> {
67 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
68 let version = deserializer.deserialize_struct(F::NAME, &["version"], VersionOnly)?;
69 Ok(SaveHeader {
70 version,
71 format: PhantomData,
72 })
73 }
74}
75
76#[derive(Deserialize)]
78#[serde(field_identifier, rename_all = "lowercase")]
79enum Field {
80 Version,
81 #[serde(other)]
82 Other,
83}
84
85struct VersionOnly;
87
88impl<'de> Visitor<'de> for VersionOnly {
89 type Value = u32;
90
91 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92 f.write_str("a save with a version")
93 }
94
95 fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<u32, A::Error> {
96 let mut version = None;
97 while let Some(field) = map.next_key::<Field>()? {
98 match field {
99 Field::Version if version.is_some() => {
100 return Err(de::Error::duplicate_field("version"));
101 }
102 Field::Version => version = Some(map.next_value()?),
103 Field::Other => {
104 map.next_value::<IgnoredAny>()?;
105 }
106 }
107 }
108 version.ok_or_else(|| de::Error::missing_field("version"))
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use alloc::format;
115 use alloc::string::{String, ToString};
116
117 use serde::de::value::{Error, U32Deserializer};
118
119 use super::*;
120
121 struct Toy;
122
123 impl SaveFormat for Toy {
124 const NAME: &'static str = "ToySave";
125 const VERSION: u32 = 3;
126 }
127
128 type Header = SaveHeader<Toy>;
129
130 fn read(text: &str) -> Result<Header, String> {
131 ron::from_str(text).map_err(|e| e.to_string())
132 }
133
134 #[test]
135 fn the_header_reads_the_version_whatever_the_rest_holds() {
136 let header = read("(version:3,world:(seed:9),units:[1,2])").unwrap();
137 assert_eq!(header.version, 3);
138 assert!(header.is_current());
139 let header = read("ToySave(units:[(a:1)],era:\"old\",version:2)").unwrap();
141 assert_eq!(header.version, 2);
142 assert!(!header.is_current());
143 assert!(!read("(version:4)").unwrap().is_current());
144 }
145
146 #[test]
147 fn what_is_not_a_save_of_the_format_is_refused() {
148 assert!(read("OtherSave(version:3)").is_err());
149 assert!(read("not a save").is_err());
150 assert!(read("[3]").is_err());
151 let missing = read("(units:[1])").unwrap_err();
152 assert!(
153 missing.contains("missing field named `version`"),
154 "{missing}"
155 );
156 let twice = read("(version:3,version:4)").unwrap_err();
157 assert!(twice.contains("duplicate field named `version`"), "{twice}");
158 }
159
160 #[test]
161 fn headers_compare_and_show_their_version() {
162 let header = read("(version:2)").unwrap();
163 let copy = header;
164 assert_eq!(header, copy);
165 assert_ne!(header, read("(version:3)").unwrap());
166 assert_eq!(format!("{header:?}"), "SaveHeader { version: 2 }");
167 }
168
169 #[test]
170 fn the_error_says_a_save_was_expected() {
171 let error = Header::deserialize(U32Deserializer::<Error>::new(3)).unwrap_err();
172 assert!(
173 error.to_string().contains("expected a save with a version"),
174 "{error}"
175 );
176 }
177}