1use std::{
2 any::type_name,
3 error,
4 fmt::{self, Debug},
5 num::{ParseFloatError, ParseIntError},
6 rc::Rc,
7 str::ParseBoolError,
8};
9
10use derive_more::{Display, From};
11use jomini::common::{Date, PdsDate};
12
13use super::super::types::{GameId, GameString, HashMap};
14
15#[derive(Debug, From, Display)]
17pub enum ConversionError {
18 #[display("failed converting {:?} to {}", _0, _1)]
20 InvalidType(SaveFileValue, &'static str),
21 ParseIntError(ParseIntError),
22 ParseFloatError(ParseFloatError),
23 ParseBoolError(ParseBoolError),
24 DateError(),
25}
26
27impl error::Error for ConversionError {
28 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
29 match self {
30 Self::ParseIntError(err) => Some(err),
31 Self::ParseBoolError(err) => Some(err),
32 Self::ParseFloatError(err) => Some(err),
33 _ => None,
34 }
35 }
36}
37
38const BOOL_TRUE: &str = "yes";
40const BOOL_FALSE: &str = "no";
42
43#[derive(PartialEq, Clone, Debug, From)]
47#[from(forward)]
48pub enum SaveFileValue {
49 String(GameString),
51 Object(SaveFileObject),
53 Real(f64),
55 Integer(i64),
57 Boolean(bool),
59 Date(Date),
61}
62
63impl TryInto<String> for SaveFileValue {
64 type Error = ConversionError;
65 fn try_into(self) -> Result<String, Self::Error> {
66 match self {
67 SaveFileValue::Object(_) => Err(ConversionError::InvalidType(
68 self.clone(),
69 type_name::<String>(),
70 )),
71 SaveFileValue::Date(date) => Ok(date.iso_8601().to_string()),
72 SaveFileValue::Boolean(b) => Ok(b.to_string()),
73 SaveFileValue::Integer(i) => Ok(i.to_string()),
74 SaveFileValue::Real(f) => Ok(f.to_string()),
75 SaveFileValue::String(s) => Ok(s.to_string()),
76 }
77 }
78}
79
80impl From<String> for SaveFileValue {
81 fn from(value: String) -> Self {
82 value.as_str().into()
83 }
84}
85
86impl From<&str> for SaveFileValue {
87 fn from(value: &str) -> Self {
88 let dot_count = value.chars().filter(|&c| c == '.').count();
89 if dot_count == 2 {
90 let mut parts = value.split('.');
91 if let (Some(year), Some(month), Some(day)) = (parts.next(), parts.next(), parts.next())
92 {
93 if let (Ok(year), Ok(month), Ok(day)) = (year.parse(), month.parse(), day.parse()) {
94 if let Some(date) = Date::from_ymd_opt(year, month, day) {
95 return SaveFileValue::Date(date);
96 }
97 }
98 }
99 } else if dot_count == 1 {
100 if let Ok(f) = value.parse() {
101 return SaveFileValue::Real(f);
102 }
103 } else if dot_count == 0 {
104 if value == BOOL_TRUE {
105 return SaveFileValue::Boolean(true);
106 } else if value == BOOL_FALSE {
107 return SaveFileValue::Boolean(false);
108 } else if let Ok(int) = value.parse() {
109 return SaveFileValue::Integer(int);
110 }
111 }
112 SaveFileValue::String(Rc::from(value))
113 }
114}
115
116impl From<i32> for SaveFileValue {
117 fn from(value: i32) -> Self {
118 SaveFileValue::Integer(value as i64)
119 }
120}
121
122impl From<u32> for SaveFileValue {
123 fn from(value: u32) -> Self {
124 SaveFileValue::Integer(value as i64)
125 }
126}
127
128impl From<u64> for SaveFileValue {
129 fn from(value: u64) -> Self {
130 SaveFileValue::Integer(value as i64)
131 }
132}
133
134impl From<[u8; 4]> for SaveFileValue {
135 fn from(value: [u8; 4]) -> Self {
136 SaveFileValue::Real(f32::from_le_bytes(value) as f64)
137 }
138}
139
140impl From<[u8; 8]> for SaveFileValue {
141 fn from(value: [u8; 8]) -> Self {
142 SaveFileValue::Real(f64::from_le_bytes(value))
143 }
144}
145
146impl SaveFileValue {
147 pub fn as_string(&self) -> Result<GameString, ConversionError> {
150 match self {
151 SaveFileValue::String(s) => Ok(s.clone()),
152 _ => Err(ConversionError::InvalidType(
153 self.clone(),
154 type_name::<GameString>(),
155 )),
156 }
157 }
158
159 pub fn as_id(&self) -> Result<GameId, ConversionError> {
161 return Ok(self.as_integer()? as GameId);
162 }
163
164 pub fn as_object(&self) -> Result<&SaveFileObject, ConversionError> {
166 match self {
167 SaveFileValue::Object(o) => Ok(o),
168 _ => Err(ConversionError::InvalidType(
169 self.clone(),
170 type_name::<SaveFileObject>(),
171 )),
172 }
173 }
174
175 pub fn as_integer(&self) -> Result<i64, ConversionError> {
178 match self {
179 SaveFileValue::Integer(i) => Ok(*i),
180 SaveFileValue::Real(r) => Ok(*r as i64),
181 SaveFileValue::String(s) => {
182 Ok(s.chars().filter_map(|c| c.to_digit(10)).sum::<u32>() as i64)
184 }
185 _ => Err(ConversionError::InvalidType(
186 self.clone(),
187 type_name::<i64>(),
188 )),
189 }
190 }
191
192 pub fn as_real(&self) -> Result<f64, ConversionError> {
195 match self {
196 SaveFileValue::Real(r) => Ok(*r),
197 SaveFileValue::Integer(i) => Ok(*i as f64),
198 _ => Err(ConversionError::InvalidType(
199 self.clone(),
200 type_name::<f64>(),
201 )),
202 }
203 }
204
205 pub fn as_date(&self) -> Result<Date, ConversionError> {
207 match self {
208 SaveFileValue::Date(date) => Ok(*date),
209 SaveFileValue::Integer(int) => {
210 Date::from_binary(*int as i32).ok_or(ConversionError::DateError())
211 }
212 _ => Err(ConversionError::InvalidType(
213 self.clone(),
214 type_name::<(i16, u8, u8)>(),
215 )),
216 }
217 }
218
219 pub fn as_boolean(&self) -> Result<bool, ConversionError> {
221 match self {
222 SaveFileValue::Boolean(b) => Ok(*b),
223 _ => Err(ConversionError::InvalidType(
224 self.clone(),
225 type_name::<bool>(),
226 )),
227 }
228 }
229}
230
231pub type GameObjectMap = HashMap<String, SaveFileValue>;
235pub type GameObjectArray = Vec<SaveFileValue>;
237
238#[derive(PartialEq, Clone, Debug)]
240pub enum SaveFileObject {
241 Map(GameObjectMap),
243 Array(GameObjectArray),
245}
246
247impl SaveFileObject {
248 pub fn as_map(&self) -> Result<&GameObjectMap, ConversionError> {
254 match self {
255 SaveFileObject::Map(o) => Ok(o),
256 _ => Err(ConversionError::InvalidType(
257 SaveFileValue::Object(self.clone()),
258 type_name::<GameObjectMap>(),
259 )),
260 }
261 }
262
263 pub fn as_array(&self) -> Result<&GameObjectArray, ConversionError> {
269 match self {
270 SaveFileObject::Array(a) => Ok(a),
271 _ => Err(ConversionError::InvalidType(
272 SaveFileValue::Object(self.clone()),
273 type_name::<GameObjectArray>(),
274 )),
275 }
276 }
277
278 pub fn is_empty(&self) -> bool {
280 match self {
281 SaveFileObject::Map(m) => m.is_empty(),
282 SaveFileObject::Array(a) => a.is_empty(),
283 }
284 }
285}
286
287#[derive(Debug, From)]
288pub enum SaveObjectError {
289 ConversionError(ConversionError),
290 KeyError(KeyError),
291}
292
293pub trait GameObjectMapping {
294 fn get_err(&self, key: &str) -> Result<&SaveFileValue, KeyError>;
299 fn get_string(&self, key: &str) -> Result<GameString, SaveObjectError>;
301 fn get_object(&self, key: &str) -> Result<&SaveFileObject, SaveObjectError>;
303 fn get_integer(&self, key: &str) -> Result<i64, SaveObjectError>;
305 fn get_real(&self, key: &str) -> Result<f64, SaveObjectError>;
307 fn get_game_id(&self, key: &str) -> Result<GameId, SaveObjectError>;
309 fn get_date(&self, key: &str) -> Result<Date, SaveObjectError>;
311}
312
313impl GameObjectMapping for GameObjectMap {
314 fn get_err(&self, key: &str) -> Result<&SaveFileValue, KeyError> {
315 self.get(key) .ok_or_else(|| KeyError::MissingKey(key.to_owned(), self.clone()))
317 }
318
319 fn get_string(&self, key: &str) -> Result<GameString, SaveObjectError> {
320 Ok(self.get_err(key)?.as_string()?)
321 }
322
323 fn get_object(&self, key: &str) -> Result<&SaveFileObject, SaveObjectError> {
324 Ok(self.get_err(key)?.as_object()?)
325 }
326
327 fn get_integer(&self, key: &str) -> Result<i64, SaveObjectError> {
328 Ok(self.get_err(key)?.as_integer()?)
329 }
330
331 fn get_real(&self, key: &str) -> Result<f64, SaveObjectError> {
332 Ok(self.get_err(key)?.as_real()?)
333 }
334
335 fn get_game_id(&self, key: &str) -> Result<GameId, SaveObjectError> {
336 Ok(self.get_err(key)?.as_id()?)
337 }
338
339 fn get_date(&self, key: &str) -> Result<Date, SaveObjectError> {
340 Ok(self.get_err(key)?.as_date()?)
341 }
342}
343
344pub trait GameObjectCollection {
345 fn get_index(&self, index: usize) -> Result<&SaveFileValue, KeyError>;
346}
347
348impl GameObjectCollection for GameObjectArray {
349 fn get_index(&self, index: usize) -> Result<&SaveFileValue, KeyError> {
350 self.get(index)
351 .ok_or_else(|| KeyError::IndexError(index, self.clone()))
352 }
353}
354
355impl fmt::Display for SaveObjectError {
356 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
357 match self {
358 Self::ConversionError(e) => write!(f, "conversion error: {}", e),
359 Self::KeyError(e) => write!(f, "key error: {}", e),
360 }
361 }
362}
363
364impl error::Error for SaveObjectError {
365 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
366 match self {
367 Self::ConversionError(e) => Some(e),
368 Self::KeyError(e) => Some(e),
369 }
370 }
371}
372
373#[derive(Debug)]
374pub enum KeyError {
375 MissingKey(String, GameObjectMap),
376 IndexError(usize, GameObjectArray),
377}
378
379impl fmt::Display for KeyError {
380 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
381 match self {
382 Self::MissingKey(key, obj) => write!(f, "key {} missing from object {:?}", key, obj),
383 Self::IndexError(index, obj) => write!(f, "index {} out of range for {:?}", index, obj),
384 }
385 }
386}
387
388impl error::Error for KeyError {
389 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
390 None
391 }
392}