renvy/
serde.rs

1use crate::merge::Settings;
2
3/// Produces a String representation of a [`Settings`] object which can be written to disk.
4///
5/// This function produces a string that consists of newline-separated lines. Each line
6/// corresponds to one Key-Value pair, where the key is separated from the value with a `=`.
7/// This function is the opposite of [`deserialize()`].
8///
9/// ## Example
10///
11/// Each key-value pair is being serialized into Strings separated by `=`. All pairs
12/// are concatenated by newlines:
13///
14/// ```
15/// let settings = renvy::Settings::from([("url".into(), Some(String::from("https://example.com")))]);
16/// let serialized = renvy::serialize(&settings);
17/// assert_eq!(serialized, String::from("url=https://example.com\n"));
18/// ```
19///
20/// Settings without values will be serialized correctly, too:
21///
22/// ```
23/// let settings = renvy::Settings::from([("url".into(), None)]);
24/// let serialized = renvy::serialize(&settings);
25/// assert_eq!(serialized, String::from("url=\n"));
26/// ```
27pub fn serialize(settings: &Settings) -> String {
28    settings
29        .iter()
30        .map(|(key, value)| format!("{}={}\n", key, if let Some(x) = value { x } else { "" }))
31        .collect()
32}
33
34/// Parses a String into an object of type [`Settings`].
35///
36/// This function parses a string that consists of newline-separated lines. Each line
37/// must be one Key-Value pair, where the key is separated from the value with a `=`.
38/// This function is the opposite of [`serialize()`].
39///
40/// ## Example
41///
42/// Each key-value pair is being parsed into one entry. All pairs
43/// are collected in one variable of type renvy::Settings.
44///
45/// ```
46/// let input = String::from("url=https://example.com\n");
47/// let deserialized = renvy::deserialize(&input);
48/// assert_eq!(deserialized.get("url").unwrap(), &Some(String::from("https://example.com")));
49/// ```
50///
51/// Lines without values will be deserialized correctly, too:
52///
53/// ```
54/// let input = String::from("url=\n");
55/// let deserialized = renvy::deserialize(&input);
56/// assert_eq!(deserialized.get("url").unwrap(), &None);
57/// ```
58pub fn deserialize(input: &str) -> Settings {
59    let mut result = Settings::new();
60
61    input
62        .split('\n')
63        .filter(|line| !line.is_empty())
64        .for_each(|line| {
65            let mut splits = line.split('=').collect::<Vec<&str>>();
66            let key: String = splits.remove(0).into();
67            let value: Option<String> = if splits.is_empty() || splits.join("=") == "" {
68                None
69            } else {
70                Some(splits.join("="))
71            };
72            result.insert(key, value);
73        });
74
75    result
76}
77
78#[cfg(test)]
79mod test {
80
81    use crate::{merge, serde};
82
83    #[test]
84    fn test_seralization_one_value() {
85        let settings =
86            merge::Settings::from([("domain".into(), Some("https://benjaminsattler.net".into()))]);
87
88        let expected = String::from("domain=https://benjaminsattler.net\n");
89
90        assert_eq!(serde::serialize(&settings), expected);
91    }
92
93    #[test]
94    fn test_deseralization_one_value() {
95        let input = String::from("domain=https://benjaminsattler.net\n");
96
97        let actual = serde::deserialize(&input);
98
99        assert!(actual.contains_key("domain"));
100        assert_eq!(
101            actual.get("domain").unwrap(),
102            &Some("https://benjaminsattler.net".into())
103        );
104    }
105
106    #[test]
107    fn test_seralization_multiple_values() {
108        let settings = merge::Settings::from([
109            ("domain".into(), Some("https://benjaminsattler.net".into())),
110            ("port".into(), Some("443".into())),
111        ]);
112
113        let expected = String::from("domain=https://benjaminsattler.net\nport=443\n");
114
115        assert_eq!(serde::serialize(&settings), expected);
116    }
117
118    #[test]
119    fn test_deseralization_multiple_values() {
120        let input = String::from("port=443\ndomain=https://benjaminsattler.net\n");
121
122        let actual = serde::deserialize(&input);
123
124        assert!(actual.contains_key("domain"));
125        assert_eq!(
126            actual.get("domain").unwrap(),
127            &Some("https://benjaminsattler.net".into())
128        );
129        assert!(actual.contains_key("port"));
130        assert_eq!(actual.get("port").unwrap(), &Some("443".into()));
131    }
132
133    #[test]
134    fn test_seralization_sorting() {
135        let settings = merge::Settings::from([
136            ("port".into(), Some("443".into())),
137            ("domain".into(), Some("https://benjaminsattler.net".into())),
138        ]);
139
140        let expected = String::from("domain=https://benjaminsattler.net\nport=443\n");
141
142        assert_eq!(serde::serialize(&settings), expected);
143    }
144
145    #[test]
146    fn test_deseralization_sorting() {
147        let input = String::from("port=443\ndomain=https://benjaminsattler.net\n");
148
149        let actual = serde::deserialize(&input);
150
151        let mut actual_iter = actual.keys().into_iter();
152        assert_eq!(actual_iter.next(), Some(&"domain".into()));
153        assert_eq!(actual_iter.next(), Some(&"port".into()));
154    }
155
156    #[test]
157    fn test_seralization_equals_in_value() {
158        let settings =
159            merge::Settings::from([("domain".into(), Some("this=value_is_special".into()))]);
160
161        let expected = String::from("domain=this=value_is_special\n");
162
163        assert_eq!(serde::serialize(&settings), expected);
164    }
165
166    #[test]
167    fn test_deseralization_equals_in_value() {
168        let input = String::from("domain=this=value_is_special\n");
169
170        let actual = serde::deserialize(&input);
171
172        assert!(actual.contains_key("domain"));
173        assert_eq!(
174            actual.get("domain").unwrap(),
175            &Some("this=value_is_special".into())
176        );
177    }
178
179    #[test]
180    fn test_seralization_spaces_in_value() {
181        let settings =
182            merge::Settings::from([("domain".into(), Some("this value has spaces".into()))]);
183
184        let expected = String::from("domain=this value has spaces\n");
185
186        assert_eq!(serde::serialize(&settings), expected);
187    }
188
189    #[test]
190    fn test_deseralization_spaces_in_value() {
191        let input = String::from("domain=this value has spaces\n");
192
193        let actual = serde::deserialize(&input);
194
195        assert!(actual.contains_key("domain"));
196        assert_eq!(
197            actual.get("domain").unwrap(),
198            &Some("this value has spaces".into())
199        );
200    }
201
202    #[test]
203    fn test_seralization_empty_value() {
204        let settings = merge::Settings::from([("domain".into(), None)]);
205
206        let expected = String::from("domain=\n");
207
208        assert_eq!(serde::serialize(&settings), expected);
209    }
210
211    #[test]
212    fn test_deseralization_empty_value() {
213        let input = String::from("domain=\n");
214
215        let actual = serde::deserialize(&input);
216
217        assert!(actual.contains_key("domain"));
218        assert_eq!(actual.get("domain").unwrap(), &None);
219    }
220}