distant-net 0.20.0

Network library for distant, providing implementations to support client/server architecture
Documentation
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
        );
    }
}