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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::fmt;
use std::ops::{Deref, DerefMut};
use std::str::FromStr;

use derive_more::{Display, Error, From, IntoIterator};
use serde::de::Deserializer;
use serde::ser::Serializer;
use serde::{Deserialize, Serialize};

use crate::common::utils::{deserialize_from_str, serialize_to_str};

/// Contains map information for connections and other use cases
#[derive(Clone, Debug, From, IntoIterator, PartialEq, Eq)]
pub struct Map(HashMap<String, String>);

impl Map {
    pub fn new() -> Self {
        Self(HashMap::new())
    }

    pub fn into_map(self) -> HashMap<String, String> {
        self.0
    }

    /// Merges this map with another map. When there is a conflict
    /// where both maps have the same key, the other map's key is
    /// used UNLESS the `keep` flag is set to true, where this
    /// map's key will be used instead.
    ///
    /// ### Examples
    ///
    /// Keeping the value will result in `x` retaining the `a` key's original value:
    ///
    /// ```rust
    /// use distant_net::map;
    ///
    /// let mut x = map!("a" -> "hello", "b" -> "world");
    /// let y = map!("a" -> "foo", "c" -> "bar");
    ///
    /// x.merge(y, /* keep */ true);
    ///
    /// assert_eq!(x, map!("a" -> "hello", "b" -> "world", "c" -> "bar"));
    /// ```
    ///
    /// Not keeping the value will result in `x` replacing the `a` key's value:
    ///
    /// ```rust
    /// use distant_net::map;
    ///
    /// let mut x = map!("a" -> "hello", "b" -> "world");
    /// let y = map!("a" -> "foo", "c" -> "bar");
    ///
    /// x.merge(y, /* keep */ false);
    ///
    /// assert_eq!(x, map!("a" -> "foo", "b" -> "world", "c" -> "bar"));
    /// ```
    pub fn merge(&mut self, other: Map, keep: bool) {
        for (key, value) in other {
            match self.0.entry(key) {
                // If we want to keep the original value, skip replacing it
                Entry::Occupied(_) if keep => continue,

                // If we want to use the other value, replace it
                Entry::Occupied(mut x) => {
                    x.insert(value);
                }

                // Otherwise, nothing found, so insert it
                Entry::Vacant(x) => {
                    x.insert(value);
                }
            }
        }
    }
}

impl Default for Map {
    fn default() -> Self {
        Self::new()
    }
}

impl Deref for Map {
    type Target = HashMap<String, String>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl DerefMut for Map {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

impl fmt::Display for Map {
    /// Outputs a `key=value` mapping in the form `key="value",key2="value2"`
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let len = self.0.len();
        for (i, (key, value)) in self.0.iter().enumerate() {
            write!(f, "{}=\"{}\"", key, encode_value(value))?;

            // Include a comma after each but the last pair
            if i + 1 < len {
                write!(f, ",")?;
            }
        }
        Ok(())
    }
}

#[derive(Clone, Debug, Display, Error)]
pub enum MapParseError {
    #[display(fmt = "Missing = after key ('{key}')")]
    MissingEqualsAfterKey { key: String },

    #[display(fmt = "Key ('{key}') must start with alphabetic character")]
    KeyMustStartWithAlphabeticCharacter { key: String },

    #[display(fmt = "Missing closing \" for value")]
    MissingClosingQuoteForValue,
}

impl FromStr for Map {
    type Err = MapParseError;

    /// Parses a series of `key=value` pairs in the form `key="value",key2=value2` where
    /// the quotes around the value are optional
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut map = HashMap::new();

        let mut s = s.trim();
        while !s.is_empty() {
            // Find {key}={tail...} where tail is everything after =
            let (key, tail) = s
                .split_once('=')
                .ok_or_else(|| MapParseError::MissingEqualsAfterKey { key: s.to_string() })?;

            // Remove whitespace around the key and ensure it starts with a proper character
            let key = key.trim();

            if !key.starts_with(char::is_alphabetic) {
                return Err(MapParseError::KeyMustStartWithAlphabeticCharacter {
                    key: key.to_string(),
                });
            }

            // Remove any map whitespace at the front of the tail
            let tail = tail.trim_start();

            // Determine if we start with a quote " otherwise we will look for the next ,
            let (value, tail) = match tail.strip_prefix('"') {
                // If quoted, we maintain the whitespace within the quotes
                Some(tail) => {
                    let mut backslash_cnt: usize = 0;
                    let mut split_idx = None;
                    for (i, b) in tail.bytes().enumerate() {
                        // If we get a backslash, increment our count
                        if b == b'\\' {
                            backslash_cnt += 1;

                        // If we get a quote and have an even number of preceding backslashes,
                        // this is considered a closing quote and we've found our split index
                        } else if b == b'"' && backslash_cnt % 2 == 0 {
                            split_idx = Some(i);
                            break;

                        // Otherwise, we've encountered some other character, so reset our backlash
                        // count
                        } else {
                            backslash_cnt = 0;
                        }
                    }

                    match split_idx {
                        Some(i) => {
                            // Splitting at idx will result in the double quote being at the
                            // beginning of the tail str, so we want to have the tail start
                            // one after the beginning of the slice
                            let (value, tail) = tail.split_at(i);
                            let tail = &tail[1..].trim_start();

                            // Also remove a trailing comma if it exists
                            let tail = tail.strip_prefix(',').unwrap_or(tail).trim_start();

                            (value, tail)
                        }
                        None => return Err(MapParseError::MissingClosingQuoteForValue),
                    }
                }

                // If not quoted, we remove all whitespace around the value
                None => match tail.split_once(',') {
                    Some((value, tail)) => (value.trim(), tail),
                    None => (tail.trim(), ""),
                },
            };

            // Insert our new pair and update the slice to be the tail (removing whitespace)
            map.insert(key.to_string(), decode_value(value));
            s = tail.trim();
        }

        Ok(Self(map))
    }
}

/// Escapes double-quotes of a value str
/// * `\` -> `\\`
/// * `"` -> `\"`
#[inline]
fn encode_value(value: &str) -> String {
    // \ -> \\
    // " -> \"
    value.replace('\\', "\\\\").replace('"', "\\\"")
}

/// Translates escaped double-quotes back into quotes
/// * `\\` -> `\`
/// * `\"` -> `"`
#[inline]
fn decode_value(value: &str) -> String {
    value.replace("\\\\", "\\").replace("\\\"", "\"")
}

impl Serialize for Map {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serialize_to_str(self, serializer)
    }
}

impl<'de> Deserialize<'de> for Map {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserialize_from_str(deserializer)
    }
}

/// Generates a new [`Map`] of key/value pairs based on literals.
///
/// ```
/// use distant_net::map;
///
/// let _map = map!("key" -> "value", "key2" -> "value2");
/// ```
#[macro_export]
macro_rules! map {
    ($($key:literal -> $value:literal),* $(,)?) => {{
        let mut _map = ::std::collections::HashMap::new();

        $(
            _map.insert($key.to_string(), $value.to_string());
        )*

        $crate::common::Map::from(_map)
    }};
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn should_support_being_parsed_from_str() {
        // Empty string (whitespace only) yields an empty map
        let map = "   ".parse::<Map>().unwrap();
        assert_eq!(map, map!());

        // Simple key=value should succeed
        let map = "key=value".parse::<Map>().unwrap();
        assert_eq!(map, map!("key" -> "value"));

        // Key can be anything but =
        let map = "key.with-characters@=value".parse::<Map>().unwrap();
        assert_eq!(map, map!("key.with-characters@" -> "value"));

        // Value can be anything but ,
        let map = "key=value.has -@#$".parse::<Map>().unwrap();
        assert_eq!(map, map!("key" -> "value.has -@#$"));

        // Value can include comma if quoted
        let map = r#"key=",,,,""#.parse::<Map>().unwrap();
        assert_eq!(map, map!("key" -> ",,,,"));

        // Supports whitespace around key and value
        let map = "  key  =  value  ".parse::<Map>().unwrap();
        assert_eq!(map, map!("key" -> "value"));

        // Supports value capturing whitespace if quoted
        let map = r#"  key  =   " value "   "#.parse::<Map>().unwrap();
        assert_eq!(map, map!("key" -> " value "));

        // Multiple key=value should succeed
        let map = "key=value,key2=value2".parse::<Map>().unwrap();
        assert_eq!(map, map!("key" -> "value", "key2" -> "value2"));

        // Quoted key=value should succeed
        let map = r#"key="value one",key2=value2"#.parse::<Map>().unwrap();
        assert_eq!(map, map!("key" -> "value one", "key2" -> "value2"));

        let map = r#"key=value,key2="value two""#.parse::<Map>().unwrap();
        assert_eq!(map, map!("key" -> "value", "key2" -> "value two"));

        let map = r#"key="value one",key2="value two""#.parse::<Map>().unwrap();
        assert_eq!(map, map!("key" -> "value one", "key2" -> "value two"));

        let map = r#"key="1,2,3",key2="4,5,6""#.parse::<Map>().unwrap();
        assert_eq!(map, map!("key" -> "1,2,3", "key2" -> "4,5,6"));

        // Dangling comma is okay
        let map = "key=value,".parse::<Map>().unwrap();
        assert_eq!(map, map!("key" -> "value"));
        let map = r#"key=",value,","#.parse::<Map>().unwrap();
        assert_eq!(map, map!("key" -> ",value,"));

        // Demonstrating greedy
        let map = "key=value key2=value2".parse::<Map>().unwrap();
        assert_eq!(map, map!("key" -> "value key2=value2"));

        // Support escaped quotes within value
        let map = r#"key="\"va\"lue\"""#.parse::<Map>().unwrap();
        assert_eq!(map, map!("key" -> r#""va"lue""#));

        // Support escaped backslashes within value
        let map = r#"key="a\\b\\c""#.parse::<Map>().unwrap();
        assert_eq!(map, map!("key" -> r"a\b\c"));

        // Backslashes and double quotes in a value are escaped together
        let map = r#"key="\"\\\"\\\"""#.parse::<Map>().unwrap();
        assert_eq!(map, map!("key" -> r#""\"\""#));

        // Variety of edge cases that should fail
        let _ = ",".parse::<Map>().unwrap_err();
        let _ = ",key=value".parse::<Map>().unwrap_err();
        let _ = "key=value,key2".parse::<Map>().unwrap_err();
    }

    #[test]
    fn should_support_being_displayed_as_a_string() {
        let map = map!().to_string();
        assert_eq!(map, "");

        let map = map!("key" -> "value").to_string();
        assert_eq!(map, r#"key="value""#);

        // Double quotes in a value are escaped
        let map = map!("key" -> r#""va"lue""#).to_string();
        assert_eq!(map, r#"key="\"va\"lue\"""#);

        // Backslashes in a value are also escaped
        let map = map!("key" -> r"a\b\c").to_string();
        assert_eq!(map, r#"key="a\\b\\c""#);

        // Backslashes and double quotes in a value are escaped together
        let map = map!("key" -> r#""\"\""#).to_string();
        assert_eq!(map, r#"key="\"\\\"\\\"""#);

        // Order of key=value output is not guaranteed
        let map = map!("key" -> "value", "key2" -> "value2").to_string();
        assert!(
            map == r#"key="value",key2="value2""# || map == r#"key2="value2",key="value""#,
            "{:?}",
            map
        );

        // Order of key=value output is not guaranteed
        let map = map!("key" -> ",", "key2" -> ",,").to_string();
        assert!(
            map == r#"key=",",key2=",,""# || map == r#"key2=",,",key=",""#,
            "{:?}",
            map
        );
    }
}