hoard 0.5.1

Hoard backups of files across your filesystem into one location.
Documentation
use std::{fmt, ops::Deref, str::FromStr};

use serde::{Deserialize, Serialize};

use super::{validate_name, Error};

/// Like [`PileName`](super::PileName), but not allowed to be empty ("anonymous")
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[repr(transparent)]
#[serde(try_from = "String")]
#[serde(into = "String")]
pub struct NonEmptyPileName(String);

impl Deref for NonEmptyPileName {
    type Target = str;

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

impl AsRef<str> for NonEmptyPileName {
    fn as_ref(&self) -> &str {
        &self.0
    }
}

impl fmt::Display for NonEmptyPileName {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.0.fmt(f)
    }
}

impl FromStr for NonEmptyPileName {
    type Err = Error;

    #[tracing::instrument(level = "trace", name = "parse_non_empty_pile_name")]
    fn from_str(value: &str) -> Result<Self, Self::Err> {
        validate_name(value.to_string()).map(Self)
    }
}

impl TryFrom<String> for NonEmptyPileName {
    type Error = Error;

    #[tracing::instrument(level = "trace", name = "non_empty_pile_name_try_from_string")]
    fn try_from(value: String) -> Result<Self, Self::Error> {
        validate_name(value).map(Self)
    }
}

impl From<NonEmptyPileName> for String {
    fn from(name: NonEmptyPileName) -> Self {
        name.0
    }
}

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

    #[test]
    #[allow(clippy::explicit_deref_methods)]
    fn test_deref() {
        let name = NonEmptyPileName(String::from("testing"));
        assert_eq!("testing", name.deref());
    }

    #[test]
    fn test_as_ref() {
        let name = NonEmptyPileName(String::from("testing"));
        assert_eq!("testing", name.as_ref());
    }

    #[test]
    fn test_to_string() {
        let s = String::from("testing");
        let name = NonEmptyPileName(s.clone());
        assert_eq!(s, name.to_string());
    }

    #[test]
    fn test_from_str_and_try_from_string() {
        let inputs = vec![
            (
                String::from(""),
                Err(Error::DisallowedName(String::from(""))),
            ),
            (
                String::from("testing"),
                Ok(NonEmptyPileName(String::from("testing"))),
            ),
            (
                String::from("config"),
                Err(Error::DisallowedName(String::from("config"))),
            ),
        ];

        for (s, expected) in inputs {
            let from_str = s.parse::<NonEmptyPileName>();
            let try_from = NonEmptyPileName::try_from(s);
            match (&from_str, &try_from, &expected) {
                (Ok(_), Err(_), _) | (Err(_), Ok(_), _) => {
                    panic!(
                        "from_str ({:?}) and try_from_string ({:?}) returned different results",
                        from_str, try_from
                    )
                }
                (Ok(result), Ok(_), Err(err)) => {
                    panic!(
                        "conversion succeeded ({}) but expected to fail with {:?}",
                        result, err
                    );
                }
                (Err(err), Err(_), Ok(result)) => {
                    panic!(
                        "conversion failed with {:?} but expected to succeed with {:?}",
                        err, result
                    );
                }
                (Ok(from_str), Ok(try_from), Ok(expected)) => {
                    assert_eq!(
                        from_str, try_from,
                        "from_str and try_from_string returned different results"
                    );
                    assert_eq!(
                        from_str, expected,
                        "expected {} but got {}",
                        expected, from_str
                    );
                }
                (Err(from_str), Err(try_from), Err(expected)) => {
                    assert_eq!(
                        from_str, try_from,
                        "from_str and try_from_string returned different errors"
                    );
                    assert_eq!(
                        from_str, expected,
                        "expected error {:?} but got {:?}",
                        expected, from_str
                    );
                }
            }
        }
    }

    #[test]
    fn test_into_string() {
        let s = String::from("testing");
        let name = NonEmptyPileName(s.clone());
        assert_eq!(s, name.to_string());
    }
}