1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
use std::{
    fs::File,
    io::{self, BufReader, Cursor, Read, Write},
    path::Path,
    str::from_utf8,
};

use crate::{
    deserialize::{
        json::{FromJson, JsonDeserializer},
        Deserialize,
    },
    error::{AnyError, IoResult},
    serialize::{
        json::{IntoJson, JsonSerializer, PrettyJsonFormatter},
        Serialize,
    },
};

pub trait Persistant: Sized + Serialize + Deserialize {
    fn save(&self, path: &str) -> Result<(), AnyError>;
    fn load(path: &str) -> Result<Self, AnyError>;
}

fn serialize_with_default<S: Serialize>(value: &S) -> Result<Vec<u8>, AnyError> {
    let mut buffer = Vec::with_capacity(128);
    let mut ser = DefaultSerializer::new(PrettyJsonFormatter::new("\t".to_string()), &mut buffer);
    value.serialize(&mut ser, &crate::position::Position::Closing)?;
    Ok(buffer)
}

fn deserialize_with_default<D: Deserialize>(value: &[u8]) -> Result<D, AnyError> {
    let mut cursor = Cursor::new(value);
    let mut des = DefaultDeserializer::new(&mut cursor);
    D::deserialize(&mut des)
}

fn serialize_factory<S: Serialize>(value: &S, path: &Path) -> Result<Vec<u8>, AnyError> {
    if let Some(ending) = path.extension() {
        if ending == "json" {
            return IntoJson::to_json(value).map(|json| json.into_bytes());
        }
    }
    serialize_with_default(value)
}

fn deserializer_factory<D: Deserialize>(value: &[u8], path: &Path) -> Result<D, AnyError> {
    if let Some(ending) = path.extension() {
        if ending == "json" {
            return FromJson::from_json(
                &from_utf8(value).expect("failed to convert content to utf8"),
            );
        }
    }
    deserialize_with_default(value)
}

type DefaultSerializer<'w> = JsonSerializer<'w, Vec<u8>, PrettyJsonFormatter>;
type DefaultDeserializer<'w> = JsonDeserializer<'w, Cursor<&'w [u8]>>;

impl<T: Sized + Serialize + Deserialize> Persistant for T {
    fn save(&self, path: &str) -> Result<(), AnyError> {
        let path = Path::new(path);
        let buffer = serialize_factory(self, path)?;
        write_bytes_file(buffer.as_slice(), path).map_err(|e| e.into())
    }

    fn load(path: &str) -> Result<Self, AnyError> {
        let path = Path::new(path);
        let content = read_bytes_file(path)?;
        deserializer_factory(content.as_slice(), path)
    }
}

fn write_bytes_file(bytes: &[u8], path: &Path) -> IoResult {
    let mut f = File::create(path)?;
    f.write_all(bytes)?;
    Ok(())
}

fn read_bytes_file(path: &Path) -> Result<Vec<u8>, io::Error> {
    let file = File::open(path)?;
    let mut reader = BufReader::new(file);
    let mut buffer = Vec::new();

    reader.read_to_end(&mut buffer)?;

    Ok(buffer)
}

#[cfg(test)]
mod test {
    use std::{
        fs::{self},
        path::Path,
    };

    use super::Persistant;

    struct FileLifetime {
        pub(crate) path: String,
    }

    impl Drop for FileLifetime {
        fn drop(&mut self) {
            let path = Path::new(&self.path);
            if path.exists() {
                if !path.is_dir() {
                    fs::remove_file(&path)
                        .expect(format!("failed to delete file: {}", self.path).as_str());
                } else {
                    fs::remove_dir_all(&path)
                        .expect(format!("failed to delete directory: {}", self.path).as_str());
                }
            }
        }
    }

    #[test]
    fn save_and_then_load_works() {
        let file_lifetime = FileLifetime {
            path: "save_i32.json".to_string(),
        };
        let data = 32i32;

        let saved = data.save(&file_lifetime.path);

        assert!(saved.is_ok());
        assert!(Path::new(&file_lifetime.path).exists());

        let loaded = i32::load(&file_lifetime.path);

        assert!(loaded.is_ok());
        assert_eq!(data, loaded.unwrap());
    }
}