use serde::Serialize;
use std::fmt;
#[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)
}
pub fn len(&self) -> usize {
self.0.len()
}
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)
}
}
}
#[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(", ")
);
}
}