carboncopy 0.3.0

A user (programmer) interface for asynchronous logging in Rust
Documentation
use serde::Serialize;
use std::fmt;

/// A sequence of validated strings used to label log entries. The characters of each tag may
/// only be alpha-numeric, hyphen (`-`), or underscore (`_`).
#[derive(Clone, Serialize)]
pub struct Tags(Vec<Tag>);

impl Tags {
    pub fn new(tags: Vec<String>) -> Result<Self, TagValidationError> {
        let mut out = Self(Vec::new());
        if tags.len() == 0 {
            return Ok(out);
        }
        for t in tags {
            if let Ok(tag) = Tag::new(t) {
                out.0.push(tag);
            } else {
                return Err(TagValidationError);
            }
        }
        Ok(out)
    }

    /// Returns the number of tags.
    pub fn len(&self) -> usize {
        self.0.len()
    }

    /// Joins the tags with separator `sep`, similar to the join method in `Vec`.
    pub fn join(&self, sep: &str) -> String {
        self.0
            .iter()
            .map(|tag| tag.0.clone())
            .collect::<Vec<String>>()
            .join(sep)
    }
}

#[derive(Clone, Serialize)]
struct Tag(String);

impl Tag {
    fn new(s: String) -> Result<Self, TagValidationError> {
        if s.chars()
            .all(|c| c.is_alphanumeric() || c == '-' || c == '_')
        {
            Ok(Self(s))
        } else {
            Err(TagValidationError)
        }
    }
}

// An error caused by a disallowed character inside a tag (in `Tags`) or `Text`.
#[derive(Debug, Clone)]
pub struct TagValidationError;

impl fmt::Display for TagValidationError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "validated string contains disallowed character")
    }
}

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

    #[test]
    fn validate_tag() {
        assert!(Tag::new("foo bar".into()).is_err());
        assert!(Tag::new("foo_-bar".into()).is_ok());
    }

    #[test]
    fn comma_delimit_tags() {
        assert_eq!(
            "foo, bar, baz",
            Tags::new(vec!["foo".into(), "bar".into(), "baz".into()])
                .unwrap()
                .join(", ")
        );
    }
}