strid 10.0.0

Improve and strengthen your strings by making them strongly-typed with less boilerplate
Documentation
#![deny(unsafe_code)]

use std::{borrow::Cow, convert::Infallible, fmt};

use strid::braid;

mod common;

#[braid]
pub struct Basic;

#[braid(ref_name = "SomeRefName")]
pub struct CustomRefName;

#[braid(validator = "ValidatedBuf")]
pub struct ExternallyValidated;

#[braid(ref_name = "SomeValidatedRefName", validator = "ValidatedBuf")]
pub struct ValidatedWithCustomRefName;

#[braid(serde)]
pub struct Orange;

#[braid]
pub struct OrangeWithNamedField {
    id: String,
}

#[test]
fn internal_access_to_named_ref_field_compile_test() {
    let x = OrangeWithNamedFieldRef::from_static("thing");
    let _ = &x.id;
}

#[braid(
    serde,
    validator,
    ref_doc = "A reference to a cool new orange, that isn't yours!"
)]
pub struct ValidatedBuf;

#[derive(Debug, PartialEq, Eq)]
pub struct InvalidData;

impl std::fmt::Display for InvalidData {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        f.write_str("found 4-byte UTF-8 codepoints")
    }
}

impl From<Infallible> for InvalidData {
    #[inline(always)]
    fn from(x: Infallible) -> Self {
        match x {}
    }
}

impl std::error::Error for InvalidData {}

impl strid::Validator for ValidatedBuf {
    type Error = InvalidData;
    fn validate(s: &str) -> Result<(), Self::Error> {
        if s.chars().any(|c| c.len_utf8() > 3) {
            Err(InvalidData)
        } else {
            Ok(())
        }
    }
}

#[braid(
    serde,
    normalizer,
    ref_doc = "A reference to a cool new orange, that isn't yours!"
)]
pub struct NormalizedBuf;

impl strid::Validator for NormalizedBuf {
    type Error = InvalidData;

    fn validate(raw: &str) -> Result<(), Self::Error> {
        if raw.chars().any(|c| c.len_utf8() > 3 || c == ' ') {
            Err(InvalidData)
        } else {
            Ok(())
        }
    }
}

impl strid::Normalizer for NormalizedBuf {
    fn normalize(s: &str) -> Result<Cow<'_, str>, Self::Error> {
        if s.chars().any(|c| c.len_utf8() > 3) {
            Err(InvalidData)
        } else if s.contains(' ') {
            Ok(Cow::Owned(s.replace(' ', "")))
        } else {
            Ok(Cow::Borrowed(s))
        }
    }
}

#[braid(clone = "omit", debug = "omit", display = "omit")]
pub struct CustomImpls;

impl fmt::Debug for CustomImpls {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str("Owned Debug")
    }
}

impl fmt::Display for CustomImpls {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str("Owned Display")
    }
}

impl fmt::Debug for CustomImplsRef {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str("Borrowed Debug")
    }
}

impl fmt::Display for CustomImplsRef {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str("Borrowed Display")
    }
}

#[braid(debug = "owned", display = "owned")]
pub struct DelegatedImpls;

impl fmt::Debug for DelegatedImplsRef {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str("Borrowed Debug")
    }
}

impl fmt::Display for DelegatedImplsRef {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str("Borrowed Display")
    }
}

#[braid(debug = "owned", display = "owned")]
pub struct Secret;

impl fmt::Debug for SecretRef {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if f.alternate() {
            f.write_str("\"")?;
            let max_len = f.width().unwrap_or(10);
            if max_len <= 1 {
                f.write_str("")?;
            } else {
                match self.0.char_indices().nth(max_len - 2) {
                    Some((idx, c)) if idx + c.len_utf8() < self.0.len() => {
                        f.write_str(&self.0[0..idx + c.len_utf8()])?;
                        f.write_str("")?;
                    }
                    _ => {
                        f.write_str(&self.0)?;
                    }
                }
            }
            f.write_str("\"")
        } else {
            f.write_str("***SECRET***")
        }
    }
}

impl fmt::Display for SecretRef {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if f.alternate() {
            f.write_str(&self.0)
        } else {
            f.write_str("***SECRET***")
        }
    }
}

mod tests {
    use super::*;

    #[test]
    fn check_custom_no_impl_clone() {
        static_assertions::assert_not_impl_any!(CustomImpls: Clone);
    }

    #[test]
    fn check_custom_debug() {
        let v = CustomImpls::from_static("");
        let vref: &CustomImplsRef = &v;
        assert_eq!("Owned Debug", format!("{:?}", v));
        assert_eq!("Borrowed Debug", format!("{:?}", vref));
    }

    #[test]
    fn check_custom_display() {
        let v = CustomImpls::from_static("");
        let vref: &CustomImplsRef = &v;
        assert_eq!("Owned Display", format!("{}", v));
        assert_eq!("Borrowed Display", format!("{}", vref));
    }

    #[test]
    fn check_delegated_impl_clone() {
        static_assertions::assert_impl_all!(DelegatedImpls: Clone);
    }

    #[test]
    fn check_delegated_debug() {
        let v = DelegatedImpls::from_static("");
        let vref: &DelegatedImplsRef = &v;
        assert_eq!("Borrowed Debug", format!("{:?}", v));
        assert_eq!("Borrowed Debug", format!("{:?}", vref));
    }

    #[test]
    fn check_delegated_display() {
        let v = DelegatedImpls::from_static("");
        let vref: &DelegatedImplsRef = &v;
        assert_eq!("Borrowed Display", format!("{}", v));
        assert_eq!("Borrowed Display", format!("{}", vref));
    }

    #[test]
    fn check_secret_impl_clone() {
        static_assertions::assert_impl_all!(Secret: Clone);
    }

    #[test]
    fn check_secret_debug() {
        let v = Secret::from_static("my secret is bananas");
        let vref: &SecretRef = &v;
        assert_eq!("***SECRET***", format!("{:?}", v));
        assert_eq!("\"my secret…\"", format!("{:#?}", v));
        assert_eq!("\"\"", format!("{:#1?}", v));
        assert_eq!("\"my secret is…\"", format!("{:#13?}", v));
        assert_eq!("\"my secret is banana…\"", format!("{:#20?}", v));
        assert_eq!("\"my secret is bananas\"", format!("{:#21?}", v));

        assert_eq!("***SECRET***", format!("{:?}", vref));
        assert_eq!("\"my secret…\"", format!("{:#?}", vref));
        assert_eq!("\"\"", format!("{:#1?}", vref));
        assert_eq!("\"my secret is…\"", format!("{:#13?}", vref));
        assert_eq!("\"my secret is banana…\"", format!("{:#20?}", vref));
        assert_eq!("\"my secret is bananas\"", format!("{:#21?}", vref));
    }

    #[test]
    fn check_secret_display() {
        let v = Secret::from_static("my secret is bananas");
        let vref: &SecretRef = &v;
        assert_eq!("***SECRET***", format!("{}", v));
        assert_eq!("my secret is bananas", format!("{:#}", v));
        assert_eq!("***SECRET***", format!("{}", vref));
        assert_eq!("my secret is bananas", format!("{:#}", vref));
    }
}