use crate::errors::OrionError;
pub(crate) const MAX_ID_LEN: usize = 128;
pub(crate) const MAX_NAME_LEN: usize = 255;
pub(crate) const MAX_DESCRIPTION_LEN: usize = 2048;
pub(crate) const MAX_VARCHAR_FIELD_LEN: usize = 255;
pub(crate) fn is_valid_identifier(s: &str) -> bool {
let mut chars = s.chars();
match chars.next() {
Some(c) if c.is_ascii_alphanumeric() => {}
_ => return false,
}
chars.all(|c| c.is_ascii_alphanumeric() || c == '.' || c == '-' || c == '_')
}
pub(crate) const RESERVED_IDS: &[&str] = &[
"import",
"export",
"validate",
"versions",
"status",
"rollout",
"test",
"circuit-breakers",
"purge",
"requeue",
"reload",
];
pub(crate) fn validate_id(id: &str, path: &'static str) -> Result<(), OrionError> {
if id.len() > MAX_ID_LEN {
return Err(OrionError::invalid_field(
path,
"INVALID",
format!("ID exceeds maximum length of {MAX_ID_LEN} characters"),
));
}
if !is_valid_identifier(id) {
return Err(OrionError::invalid_field(
path,
"INVALID",
"ID must start with an alphanumeric character and contain only alphanumeric characters, dots, hyphens, or underscores",
));
}
if RESERVED_IDS.contains(&id.to_ascii_lowercase().as_str()) {
return Err(OrionError::invalid_field(
path,
"INVALID",
format!(
"'{id}' is reserved: the admin API serves a static sub-resource at that \
path, so an entity with this id could not be addressed through /{{id}}"
),
));
}
Ok(())
}
pub(crate) fn validate_name(name: &str, path: &'static str) -> Result<(), OrionError> {
if name.trim().is_empty() {
return Err(OrionError::invalid_field(
path,
"INVALID",
"Name must not be empty",
));
}
if name.len() > MAX_NAME_LEN {
return Err(OrionError::invalid_field(
path,
"INVALID",
format!("Name exceeds maximum length of {MAX_NAME_LEN} characters"),
));
}
Ok(())
}
pub(crate) fn validate_description(desc: &str, path: &'static str) -> Result<(), OrionError> {
if desc.len() > MAX_DESCRIPTION_LEN {
return Err(OrionError::invalid_field(
path,
"INVALID",
format!("Description exceeds maximum length of {MAX_DESCRIPTION_LEN} characters"),
));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_valid_id() {
assert!(validate_id("my-workflow-1", "entity.id").is_ok());
assert!(validate_id("workflow.v2", "entity.id").is_ok());
assert!(validate_id("A123_test", "entity.id").is_ok());
}
#[test]
fn test_invalid_id_chars() {
assert!(validate_id("", "entity.id").is_err());
assert!(validate_id("-starts-with-dash", "entity.id").is_err());
assert!(validate_id(".starts-with-dot", "entity.id").is_err());
assert!(validate_id("has spaces", "entity.id").is_err());
assert!(validate_id("has/slash", "entity.id").is_err());
}
#[test]
fn test_id_too_long() {
let long_id = "a".repeat(MAX_ID_LEN + 1);
assert!(validate_id(&long_id, "entity.id").is_err());
}
#[test]
fn test_valid_name() {
assert!(validate_name("My Workflow", "entity.name").is_ok());
}
#[test]
fn test_empty_name() {
assert!(validate_name("", "entity.name").is_err());
assert!(validate_name(" ", "entity.name").is_err());
}
#[test]
fn test_name_too_long() {
let long_name = "a".repeat(MAX_NAME_LEN + 1);
assert!(validate_name(&long_name, "entity.name").is_err());
}
#[test]
fn test_description_too_long() {
let long_desc = "a".repeat(MAX_DESCRIPTION_LEN + 1);
assert!(validate_description(&long_desc, "entity.description").is_err());
}
#[test]
fn test_description_valid() {
assert!(validate_description("A short description", "entity.description").is_ok());
assert!(validate_description("", "entity.description").is_ok());
}
}