Skip to main content

tagotip_codec/
validate.rs

1use crate::consts;
2use crate::error::{ParseError, ParseErrorKind};
3
4/// Validate a variable name: lowercase a-z, digits, underscore. Max 100 bytes.
5pub fn validate_varname(name: &str, pos: usize) -> Result<(), ParseError> {
6  if name.is_empty() {
7    return Err(ParseError::new(ParseErrorKind::InvalidField, pos));
8  }
9  if name.len() > consts::MAX_VARNAME_LEN {
10    return Err(ParseError::new(ParseErrorKind::InvalidField, pos));
11  }
12  for &b in name.as_bytes() {
13    if !(b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'_') {
14      return Err(ParseError::new(ParseErrorKind::InvalidField, pos));
15    }
16  }
17  Ok(())
18}
19
20/// Validate a serial number: alphanumeric, hyphen, underscore. Max 100 bytes.
21pub fn validate_serial(serial: &str, pos: usize) -> Result<(), ParseError> {
22  if serial.is_empty() {
23    return Err(ParseError::new(ParseErrorKind::InvalidSerial, pos));
24  }
25  if serial.len() > consts::MAX_SERIAL_LEN {
26    return Err(ParseError::new(ParseErrorKind::InvalidSerial, pos));
27  }
28  for &b in serial.as_bytes() {
29    if !(b.is_ascii_alphanumeric() || b == b'-' || b == b'_') {
30      return Err(ParseError::new(ParseErrorKind::InvalidSerial, pos));
31    }
32  }
33  Ok(())
34}
35
36/// Validate a group name: same rules as variable name. Max 100 bytes.
37pub fn validate_group(group: &str, pos: usize) -> Result<(), ParseError> {
38  if group.is_empty() {
39    return Err(ParseError::new(ParseErrorKind::InvalidField, pos));
40  }
41  if group.len() > consts::MAX_GROUP_LEN {
42    return Err(ParseError::new(ParseErrorKind::InvalidField, pos));
43  }
44  for &b in group.as_bytes() {
45    if !(b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'_') {
46      return Err(ParseError::new(ParseErrorKind::InvalidField, pos));
47    }
48  }
49  Ok(())
50}
51
52/// Validate a metadata key: same rules as variable name. Max 100 bytes.
53pub fn validate_meta_key(key: &str, pos: usize) -> Result<(), ParseError> {
54  if key.is_empty() {
55    return Err(ParseError::new(ParseErrorKind::InvalidMetadata, pos));
56  }
57  if key.len() > consts::MAX_META_KEY_LEN {
58    return Err(ParseError::new(ParseErrorKind::InvalidMetadata, pos));
59  }
60  for &b in key.as_bytes() {
61    if !(b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'_') {
62      return Err(ParseError::new(ParseErrorKind::InvalidMetadata, pos));
63    }
64  }
65  Ok(())
66}
67
68/// Validate a unit string: non-empty, max 25 bytes.
69pub fn validate_unit(unit: &str, pos: usize) -> Result<(), ParseError> {
70  if unit.is_empty() {
71    return Err(ParseError::new(ParseErrorKind::InvalidField, pos));
72  }
73  if unit.len() > consts::MAX_UNIT_LEN {
74    return Err(ParseError::new(ParseErrorKind::InvalidField, pos));
75  }
76  Ok(())
77}
78
79/// Validate number format per spec: `-?(0|[1-9][0-9]*)(\.[0-9]+)?`
80pub fn validate_number(s: &str, pos: usize) -> Result<(), ParseError> {
81  let bytes = s.as_bytes();
82  let mut i = 0;
83
84  if i < bytes.len() && bytes[i] == b'-' {
85    i += 1;
86  }
87
88  if i >= bytes.len() {
89    return Err(ParseError::new(ParseErrorKind::InvalidVariable, pos));
90  }
91
92  // int-part: "0" / (%x31-39 *DIGIT)
93  if bytes[i] == b'0' {
94    i += 1;
95  } else if bytes[i] >= b'1' && bytes[i] <= b'9' {
96    i += 1;
97    while i < bytes.len() && bytes[i].is_ascii_digit() {
98      i += 1;
99    }
100  } else {
101    return Err(ParseError::new(ParseErrorKind::InvalidVariable, pos));
102  }
103
104  // Optional decimal fraction
105  if i < bytes.len() && bytes[i] == b'.' {
106    i += 1;
107    if i >= bytes.len() || !bytes[i].is_ascii_digit() {
108      return Err(ParseError::new(ParseErrorKind::InvalidVariable, pos));
109    }
110    while i < bytes.len() && bytes[i].is_ascii_digit() {
111      i += 1;
112    }
113  }
114
115  if i != bytes.len() {
116    return Err(ParseError::new(ParseErrorKind::InvalidVariable, pos));
117  }
118
119  Ok(())
120}