#![cfg_attr(test, allow(clippy::expect_used, clippy::unwrap_used))]
pub const MAX_NAME_LENGTH: usize = 64;
pub const MAX_DESCRIPTION_LENGTH: usize = 1024;
pub fn validate_name(name: &str) -> Vec<String> {
let mut errors = Vec::new();
if name.len() > MAX_NAME_LENGTH {
errors.push(format!(
"name exceeds {MAX_NAME_LENGTH} characters ({} got)",
name.len()
));
}
if !name
.chars()
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-')
{
errors.push(
"name contains invalid characters (must be lowercase a-z, 0-9, hyphens only)".into(),
);
}
if name.starts_with('-') || name.ends_with('-') {
errors.push("name must not start or end with a hyphen".into());
}
if name.contains("--") {
errors.push("name must not contain consecutive hyphens".into());
}
errors
}
pub fn validate_description(description: Option<&str>) -> Vec<String> {
let mut errors = Vec::new();
match description.map(str::trim) {
None | Some("") => errors.push("description is required".into()),
Some(d) if d.len() > MAX_DESCRIPTION_LENGTH => {
errors.push(format!(
"description exceeds {MAX_DESCRIPTION_LENGTH} characters ({} got)",
d.len()
));
}
_ => {}
}
errors
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn valid_name_passes() {
assert_eq!(validate_name("rust-error-triage"), Vec::<String>::new());
assert_eq!(validate_name("a"), Vec::<String>::new());
assert_eq!(validate_name("a1-b2-c3"), Vec::<String>::new());
}
#[test]
fn name_too_long_fails() {
let n = "a".repeat(MAX_NAME_LENGTH + 1);
let errors = validate_name(&n);
assert!(errors.iter().any(|e| e.contains("exceeds")), "{errors:?}");
}
#[test]
fn name_uppercase_fails() {
let errors = validate_name("Foo");
assert!(
errors.iter().any(|e| e.contains("invalid characters")),
"{errors:?}"
);
}
#[test]
fn name_underscore_fails() {
let errors = validate_name("rust_error_triage");
assert!(
errors.iter().any(|e| e.contains("invalid characters")),
"{errors:?}"
);
}
#[test]
fn name_leading_hyphen_fails() {
let errors = validate_name("-foo");
assert!(
errors
.iter()
.any(|e| e.contains("start or end with a hyphen")),
"{errors:?}"
);
}
#[test]
fn name_trailing_hyphen_fails() {
let errors = validate_name("foo-");
assert!(
errors
.iter()
.any(|e| e.contains("start or end with a hyphen")),
"{errors:?}"
);
}
#[test]
fn name_consecutive_hyphens_fail() {
let errors = validate_name("foo--bar");
assert!(
errors.iter().any(|e| e.contains("consecutive hyphens")),
"{errors:?}"
);
}
#[test]
fn valid_description_passes() {
assert_eq!(validate_description(Some("ok")), Vec::<String>::new());
}
#[test]
fn missing_description_fails() {
assert!(validate_description(None)
.iter()
.any(|e| e.contains("required")));
assert!(validate_description(Some(""))
.iter()
.any(|e| e.contains("required")));
assert!(validate_description(Some(" "))
.iter()
.any(|e| e.contains("required")));
}
#[test]
fn description_too_long_fails() {
let d = "x".repeat(MAX_DESCRIPTION_LENGTH + 1);
assert!(validate_description(Some(&d))
.iter()
.any(|e| e.contains("exceeds")));
}
}