//! Small pure validation helpers shared across value objects. use crate::error::DomainError; /// Returns the trimmed string if non-empty, otherwise [`DomainError::EmptyField`]. pub(crate) fn non_empty(value: &str, field: &'static str) -> Result<(), DomainError> { if value.trim().is_empty() { Err(DomainError::EmptyField { field }) } else { Ok(()) } } /// Validates that `var` is a syntactically valid environment-variable identifier: /// non-empty, starts with a letter or `_`, and contains only ASCII alphanumeric /// characters or `_`. pub(crate) fn valid_env_var(var: &str) -> Result<(), DomainError> { let invalid = || DomainError::InvalidEnvVar { value: var.to_string(), }; let mut chars = var.chars(); match chars.next() { Some(c) if c.is_ascii_alphabetic() || c == '_' => {} _ => return Err(invalid()), } if chars.all(|c| c.is_ascii_alphanumeric() || c == '_') { Ok(()) } else { Err(invalid()) } } /// Validates that a path is relative and does not escape its root via `..`. /// /// Used for [`crate::profile::ContextInjection::ConventionFile`] targets and /// manifest `.md` paths, which must stay inside the project / `.ideai/` tree. pub(crate) fn relative_safe(path: &str) -> Result<(), DomainError> { let err = || DomainError::PathNotRelativeSafe { path: path.to_string(), }; if path.is_empty() { return Err(err()); } // Reject absolute POSIX paths and Windows drive / UNC paths. let bytes = path.as_bytes(); if bytes[0] == b'/' || bytes[0] == b'\\' { return Err(err()); } if path.len() >= 2 && bytes[1] == b':' && bytes[0].is_ascii_alphabetic() { return Err(err()); } // Reject any `..` traversal component (handle both separators). for component in path.split(['/', '\\']) { if component == ".." { return Err(err()); } } Ok(()) }