toon/decode/
validation.rs1use crate::decode::parser::ArrayHeaderInfo;
2use crate::decode::scanner::{BlankLineInfo, Depth, ParsedLine};
3use crate::error::{Result, ToonError};
4use crate::shared::constants::{COLON, LIST_ITEM_PREFIX};
5use crate::shared::string_utils::find_unquoted_char;
6
7pub fn assert_expected_count(
13 actual: usize,
14 expected: usize,
15 item_type: &str,
16 strict: bool,
17) -> Result<()> {
18 if strict && actual != expected {
19 return Err(ToonError::message(format!(
20 "Expected {expected} {item_type}, but got {actual}"
21 )));
22 }
23 Ok(())
24}
25
26pub fn validate_no_extra_list_items(
32 next_line: Option<&ParsedLine>,
33 item_depth: Depth,
34 expected_count: usize,
35 strict: bool,
36) -> Result<()> {
37 if strict {
38 if let Some(line) = next_line {
39 if line.depth == item_depth && line.content.starts_with(LIST_ITEM_PREFIX) {
40 return Err(ToonError::message(format!(
41 "Expected {expected_count} list array items, but found more"
42 )));
43 }
44 }
45 }
46 Ok(())
47}
48
49pub fn validate_no_extra_tabular_rows(
55 next_line: Option<&ParsedLine>,
56 row_depth: Depth,
57 header: &ArrayHeaderInfo,
58 strict: bool,
59) -> Result<()> {
60 if strict {
61 if let Some(line) = next_line {
62 if line.depth == row_depth
63 && !line.content.starts_with(LIST_ITEM_PREFIX)
64 && is_data_row(&line.content, header.delimiter)
65 {
66 return Err(ToonError::message(format!(
67 "Expected {} tabular rows, but found more",
68 header.length
69 )));
70 }
71 }
72 }
73 Ok(())
74}
75
76pub fn validate_no_blank_lines_in_range(
82 start_line: usize,
83 end_line: usize,
84 blank_lines: &[BlankLineInfo],
85 strict: bool,
86 context: &str,
87) -> Result<()> {
88 if !strict {
89 return Ok(());
90 }
91
92 if let Some(first_blank) = blank_lines
93 .iter()
94 .find(|blank| blank.line_number > start_line && blank.line_number < end_line)
95 {
96 return Err(ToonError::message(format!(
97 "Line {}: Blank lines inside {context} are not allowed in strict mode",
98 first_blank.line_number
99 )));
100 }
101
102 Ok(())
103}
104
105fn is_data_row(content: &str, delimiter: char) -> bool {
106 let colon_pos = find_unquoted_char(content, COLON, 0);
108 let delimiter_pos = find_unquoted_char(content, delimiter, 0);
109
110 if colon_pos.is_none() {
112 return true;
113 }
114
115 if let Some(delimiter_pos) = delimiter_pos {
117 if let Some(colon_pos) = colon_pos {
118 return delimiter_pos < colon_pos;
119 }
120 }
121
122 false
123}