Skip to main content

agentics_contracts/validation/
text.rs

1//! Shared text validation for public request and manifest fields.
2
3use agentics_error::{Result, ServiceError};
4
5/// Validate that a string field contains visible non-whitespace content.
6pub fn require_non_empty(value: &str, field: &str) -> Result<()> {
7    if value.trim().is_empty() {
8        return Err(ServiceError::Validation(format!(
9            "{field} must not be empty"
10        )));
11    }
12
13    Ok(())
14}
15
16/// Validate display text that is bounded by UTF-8 bytes and excludes binary controls.
17pub fn validate_bounded_display_text(value: &str, field: &str, max_bytes: usize) -> Result<()> {
18    if value.len() > max_bytes {
19        return Err(ServiceError::Validation(format!(
20            "{field} must be at most {max_bytes} UTF-8 bytes"
21        )));
22    }
23    if value.chars().any(is_disallowed_display_text_char) {
24        return Err(ServiceError::Validation(format!(
25            "{field} must not contain non-text control characters"
26        )));
27    }
28
29    Ok(())
30}
31
32/// Validate submitter-visible note text from `agentics.solution.json`.
33pub fn validate_solution_note(note: &str, max_bytes: usize) -> Result<()> {
34    validate_bounded_display_text(note, "note", max_bytes)
35}
36
37/// Return whether a decoded text character is not safe display text.
38fn is_disallowed_display_text_char(ch: char) -> bool {
39    ch.is_control() && !matches!(ch, '\n' | '\r' | '\t')
40}
41
42#[cfg(test)]
43mod tests {
44    use super::{require_non_empty, validate_solution_note};
45
46    #[test]
47    fn validates_display_text_bounds_and_controls() {
48        validate_solution_note("normal note\nwith tab\t", 1024).expect("text note should pass");
49
50        let oversized = "x".repeat(1025);
51        assert!(validate_solution_note(&oversized, 1024).is_err());
52        assert!(validate_solution_note("bad\u{0007}", 1024).is_err());
53    }
54
55    #[test]
56    fn rejects_empty_visible_text() {
57        assert!(require_non_empty("value", "field").is_ok());
58        assert!(require_non_empty(" \n\t", "field").is_err());
59    }
60}