toon_format/decode/
validation.rs

1use crate::types::{
2    ToonError,
3    ToonResult,
4};
5
6/// Validate that array length matches expected value.
7pub fn validate_array_length(expected: usize, actual: usize) -> ToonResult<()> {
8    // Array length mismatches should always error, regardless of strict mode
9    if expected != actual {
10        return Err(ToonError::length_mismatch(expected, actual));
11    }
12    Ok(())
13}
14
15/// Validate field list for tabular arrays (no duplicates, non-empty names).
16pub fn validate_field_list(fields: &[String]) -> ToonResult<()> {
17    if fields.is_empty() {
18        return Err(ToonError::InvalidInput(
19            "Field list cannot be empty for tabular arrays".to_string(),
20        ));
21    }
22
23    // Check for duplicate field names
24    for i in 0..fields.len() {
25        for j in (i + 1)..fields.len() {
26            if fields[i] == fields[j] {
27                return Err(ToonError::InvalidInput(format!(
28                    "Duplicate field name: '{}'",
29                    fields[i]
30                )));
31            }
32        }
33    }
34
35    for field in fields {
36        if field.is_empty() {
37            return Err(ToonError::InvalidInput(
38                "Field name cannot be empty".to_string(),
39            ));
40        }
41    }
42
43    Ok(())
44}
45
46/// Validate that a tabular row has the expected number of values.
47pub fn validate_row_length(
48    row_index: usize,
49    expected_fields: usize,
50    actual_values: usize,
51) -> ToonResult<()> {
52    if expected_fields != actual_values {
53        return Err(ToonError::InvalidStructure(format!(
54            "Row {row_index} has {actual_values} values but expected {expected_fields} fields"
55        )));
56    }
57    Ok(())
58}
59
60/// Validate that detected and expected delimiters match.
61pub fn validate_delimiter_consistency(
62    detected: Option<crate::types::Delimiter>,
63    expected: Option<crate::types::Delimiter>,
64) -> ToonResult<()> {
65    if let (Some(detected), Some(expected)) = (detected, expected) {
66        if detected != expected {
67            return Err(ToonError::InvalidDelimiter(format!(
68                "Detected delimiter {detected} but expected {expected}"
69            )));
70        }
71    }
72    Ok(())
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    #[test]
80    fn test_validate_array_length() {
81        assert!(validate_array_length(5, 3).is_err());
82        assert!(validate_array_length(3, 5).is_err());
83        assert!(validate_array_length(5, 5).is_ok());
84    }
85
86    #[test]
87    fn test_validate_field_list() {
88        assert!(validate_field_list(&["id".to_string(), "name".to_string()]).is_ok());
89        assert!(validate_field_list(&["field1".to_string()]).is_ok());
90
91        assert!(validate_field_list(&[]).is_err());
92
93        assert!(
94            validate_field_list(&["id".to_string(), "name".to_string(), "id".to_string()]).is_err()
95        );
96
97        assert!(
98            validate_field_list(&["id".to_string(), "".to_string(), "name".to_string()]).is_err()
99        );
100    }
101
102    #[test]
103    fn test_validate_row_length() {
104        assert!(validate_row_length(0, 3, 3).is_ok());
105        assert!(validate_row_length(1, 5, 5).is_ok());
106
107        assert!(validate_row_length(0, 3, 2).is_err());
108        assert!(validate_row_length(1, 3, 4).is_err());
109    }
110
111    #[test]
112    fn test_validate_delimiter_consistency() {
113        use crate::types::Delimiter;
114
115        assert!(
116            validate_delimiter_consistency(Some(Delimiter::Comma), Some(Delimiter::Comma)).is_ok()
117        );
118
119        assert!(
120            validate_delimiter_consistency(Some(Delimiter::Comma), Some(Delimiter::Pipe)).is_err()
121        );
122
123        assert!(validate_delimiter_consistency(None, Some(Delimiter::Comma)).is_ok());
124        assert!(validate_delimiter_consistency(Some(Delimiter::Comma), None).is_ok());
125        assert!(validate_delimiter_consistency(None, None).is_ok());
126    }
127}