feat: Add tag engine module with tag validation and parsing
This commit is contained in:
parent
0dc401f642
commit
06a25589b9
3 changed files with 118 additions and 0 deletions
|
|
@ -4,3 +4,4 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
thiserror = "1.0"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
mod tag_engine;
|
||||
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
|
|
|
|||
115
src/tag_engine.rs
Normal file
115
src/tag_engine.rs
Normal 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(':')))
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue