xyz_rust/spec/
validate.rs1use crate::errors;
6use crate::spec::XyzField;
7use crate::spec::field::FieldMeta;
8
9#[derive(Debug, Clone, PartialEq)]
11pub struct VRule {
12 pub key: String,
13 pub args: Vec<String>,
14 pub num: f64,
15}
16
17pub const NUMERIC_RULE_KEYS: &[&str] = &["min", "max", "len", "gt", "gte", "lt", "lte"];
19
20pub const SUPPORTED_RULE_KEYS: &[&str] = &[
22 "required",
23 "omitempty",
24 "min",
25 "max",
26 "len",
27 "gt",
28 "gte",
29 "lt",
30 "lte",
31 "oneof",
32 "email",
33];
34
35pub fn parse_validate_tag(v: &str) -> errors::Result<Vec<VRule>> {
38 if v.trim().is_empty() {
39 return Ok(Vec::new());
40 }
41 let mut out = Vec::new();
42 for part in v.split(',').map(str::trim) {
43 if part.is_empty() {
44 continue;
45 }
46 let (key, rest) = match part.split_once('=') {
47 Some((k, r)) => (k.trim(), r),
48 None => (part, ""),
49 };
50 if !SUPPORTED_RULE_KEYS.contains(&key) {
51 return Err(errors::Error::new(
52 errors::Kind::Internal,
53 format!(
54 "unsupported validate rule {key:?} (supported: required, omitempty, min, max, len, gt, gte, lt, lte, oneof, email)"
55 ),
56 ));
57 }
58 let mut rule = VRule {
59 key: key.to_string(),
60 args: rest.split_whitespace().map(str::to_string).collect(),
61 num: -1.0,
62 };
63 if NUMERIC_RULE_KEYS.contains(&key) {
64 if rule.args.len() != 1 {
65 return Err(errors::Error::new(
66 errors::Kind::Internal,
67 format!(
68 "validate rule {key:?} needs exactly one numeric argument, got {rest:?}"
69 ),
70 ));
71 }
72 let n = rule.args[0].parse::<f64>().map_err(|_| {
73 errors::Error::new(
74 errors::Kind::Internal,
75 format!("validate rule {key:?}: {:?} is not a number", rule.args[0]),
76 )
77 })?;
78 rule.num = n;
79 rule.args.clear(); }
81 out.push(rule);
82 }
83 Ok(out)
84}
85
86pub fn field_rule_error(f: &FieldMeta, rule: &str) -> errors::Error {
88 let name = if f.json_name.is_empty() {
89 f.name.as_str()
90 } else {
91 f.json_name.as_str()
92 };
93 errors::Error::new(
94 errors::Kind::InvalidInput,
95 format!("invalid value for field {name:?}: {rule}"),
96 )
97}
98
99pub fn check_rules<T: XyzField>(f: &FieldMeta, v: &T) -> errors::Result<()> {
103 if f.rules.is_empty() {
104 return Ok(());
105 }
106 let zero = v.xyz_is_zero();
107 for r in &f.rules {
108 match r.key.as_str() {
109 "omitempty" => {
110 if zero {
111 break; }
113 continue;
114 }
115 "required" => {
116 if zero {
117 return Err(field_rule_error(f, "required"));
118 }
119 continue;
120 }
121 _ => {
122 if !v.xyz_rule_ok(r) {
123 return Err(field_rule_error(f, &r.key));
124 }
125 }
126 }
127 }
128 Ok(())
129}
130
131pub fn check_opt_rules<T: XyzField>(f: &FieldMeta, v: Option<&T>) -> errors::Result<()> {
134 if f.rules.is_empty() {
135 return Ok(());
136 }
137 let zero = v.is_none();
138 for r in &f.rules {
139 match r.key.as_str() {
140 "omitempty" => {
141 if zero {
142 break;
143 }
144 continue;
145 }
146 "required" => {
147 if zero {
148 return Err(field_rule_error(f, "required"));
149 }
150 continue;
151 }
152 _ => match v {
153 Some(inner) => {
154 if !inner.xyz_rule_ok(r) {
155 return Err(field_rule_error(f, &r.key));
156 }
157 }
158 None => {
159 return Err(field_rule_error(f, &r.key));
160 }
161 },
162 }
163 }
164 Ok(())
165}
166
167pub fn check_vec_rules<T: XyzField>(f: &FieldMeta, v: &[T]) -> errors::Result<()> {
169 if f.rules.is_empty() {
170 return Ok(());
171 }
172 let zero = v.is_empty();
173 let fmt = format!(
174 "[{}]",
175 v.iter().map(|x| x.xyz_fmt()).collect::<Vec<_>>().join(" ")
176 );
177 for r in &f.rules {
178 match r.key.as_str() {
179 "omitempty" => {
180 if zero {
181 break;
182 }
183 continue;
184 }
185 "required" => {
186 if zero {
187 return Err(field_rule_error(f, "required"));
188 }
189 continue;
190 }
191 "min" | "max" | "len" => {
192 let n = v.len() as f64;
193 let ok = match r.key.as_str() {
194 "min" => n >= r.num,
195 "max" => n <= r.num,
196 _ => n == r.num,
197 };
198 if !ok {
199 return Err(field_rule_error(f, &r.key));
200 }
201 }
202 "oneof" => {
203 if !r.args.iter().any(|a| a == &fmt) {
204 return Err(field_rule_error(f, &r.key));
205 }
206 }
207 _ => {
208 return Err(field_rule_error(f, &r.key));
209 }
210 }
211 }
212 Ok(())
213}
214pub fn elem_ref(f: &FieldMeta) -> Option<&FieldMeta> {
216 f.elem.as_deref()
217}