feat: Add tag engine module with tag validation and parsing

This commit is contained in:
Moritz Böhme 2025-02-23 15:41:32 +01:00
parent 0dc401f642
commit 06a25589b9
3 changed files with 118 additions and 0 deletions

View file

@ -4,3 +4,4 @@ version = "0.1.0"
edition = "2021"
[dependencies]
thiserror = "1.0"

View file

@ -1,3 +1,5 @@
mod tag_engine;
fn main() {
println!("Hello, world!");
}

115
src/tag_engine.rs Normal file
View file

@ -0,0 +1,115 @@
use std::path::Path;
use thiserror::Error;
#[derive(Error, Debug, PartialEq)]
pub enum TagError {
#[error("tag cannot be empty")]
Empty,
#[error("tag contains invalid character: {0}")]
InvalidChar(char),
}
#[derive(Error, Debug, PartialEq)]
pub enum ParseError {
#[error("multiple tag delimiters found")]
MultipleDelimiters,
#[error("invalid tag: {0}")]
InvalidTag(#[from] TagError),
}
pub fn validate_tag(tag: &str) -> Result<(), TagError> {
if tag.is_empty() {
return Err(TagError::Empty);
}
// Check for prohibited characters
for c in tag.chars() {
if c == '\0' || c == ':' || c == '/' || c == ' ' {
return Err(TagError::InvalidChar(c));
}
}
Ok(())
}
pub fn parse_tags(filename: &str) -> Result<(String, Vec<String>), ParseError> {
const TAG_DELIMITER: &str = " -- ";
let parts: Vec<&str> = filename.split(TAG_DELIMITER).collect();
if parts.len() > 2 {
return Err(ParseError::MultipleDelimiters);
}
let base_name = parts[0].to_string();
let tags = if parts.len() == 2 {
let tag_part = parts[1];
let tags: Vec<String> = tag_part
.split_whitespace()
.map(str::to_string)
.collect();
// Validate each tag
for tag in &tags {
validate_tag(tag)?;
}
tags
} else {
Vec::new()
};
Ok((base_name, tags))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate_tag_valid() {
assert!(validate_tag("valid-tag").is_ok());
assert!(validate_tag("tag123").is_ok());
assert!(validate_tag("_tag_").is_ok());
}
#[test]
fn test_validate_tag_invalid() {
assert_eq!(validate_tag(""), Err(TagError::Empty));
assert_eq!(validate_tag("bad tag"), Err(TagError::InvalidChar(' ')));
assert_eq!(validate_tag("bad:tag"), Err(TagError::InvalidChar(':')));
assert_eq!(validate_tag("bad/tag"), Err(TagError::InvalidChar('/')));
assert_eq!(validate_tag("bad\0tag"), Err(TagError::InvalidChar('\0')));
}
#[test]
fn test_parse_tags_no_tags() {
let (base, tags) = parse_tags("file.txt").unwrap();
assert_eq!(base, "file.txt");
assert!(tags.is_empty());
}
#[test]
fn test_parse_tags_with_tags() {
let (base, tags) = parse_tags("file.txt -- tag1 tag2").unwrap();
assert_eq!(base, "file.txt");
assert_eq!(tags, vec!["tag1", "tag2"]);
}
#[test]
fn test_parse_tags_multiple_delimiters() {
assert_eq!(
parse_tags("file.txt -- tag1 -- tag2"),
Err(ParseError::MultipleDelimiters)
);
}
#[test]
fn test_parse_tags_invalid_tag() {
assert_eq!(
parse_tags("file.txt -- invalid:tag"),
Err(ParseError::InvalidTag(TagError::InvalidChar(':')))
);
}
}