use crate::errors;
use crate::spec::XyzField;
use crate::spec::field::FieldMeta;
#[derive(Debug, Clone, PartialEq)]
pub struct VRule {
pub key: String,
pub args: Vec<String>,
pub num: f64,
}
pub const NUMERIC_RULE_KEYS: &[&str] = &["min", "max", "len", "gt", "gte", "lt", "lte"];
pub const SUPPORTED_RULE_KEYS: &[&str] = &[
"required",
"omitempty",
"min",
"max",
"len",
"gt",
"gte",
"lt",
"lte",
"oneof",
"email",
];
pub fn parse_validate_tag(v: &str) -> errors::Result<Vec<VRule>> {
if v.trim().is_empty() {
return Ok(Vec::new());
}
let mut out = Vec::new();
for part in v.split(',').map(str::trim) {
if part.is_empty() {
continue;
}
let (key, rest) = match part.split_once('=') {
Some((k, r)) => (k.trim(), r),
None => (part, ""),
};
if !SUPPORTED_RULE_KEYS.contains(&key) {
return Err(errors::Error::new(
errors::Kind::Internal,
format!(
"unsupported validate rule {key:?} (supported: required, omitempty, min, max, len, gt, gte, lt, lte, oneof, email)"
),
));
}
let mut rule = VRule {
key: key.to_string(),
args: rest.split_whitespace().map(str::to_string).collect(),
num: -1.0,
};
if NUMERIC_RULE_KEYS.contains(&key) {
if rule.args.len() != 1 {
return Err(errors::Error::new(
errors::Kind::Internal,
format!(
"validate rule {key:?} needs exactly one numeric argument, got {rest:?}"
),
));
}
let n = rule.args[0].parse::<f64>().map_err(|_| {
errors::Error::new(
errors::Kind::Internal,
format!("validate rule {key:?}: {:?} is not a number", rule.args[0]),
)
})?;
rule.num = n;
rule.args.clear(); }
out.push(rule);
}
Ok(out)
}
pub fn field_rule_error(f: &FieldMeta, rule: &str) -> errors::Error {
let name = if f.json_name.is_empty() {
f.name.as_str()
} else {
f.json_name.as_str()
};
errors::Error::new(
errors::Kind::InvalidInput,
format!("invalid value for field {name:?}: {rule}"),
)
}
pub fn check_rules<T: XyzField>(f: &FieldMeta, v: &T) -> errors::Result<()> {
if f.rules.is_empty() {
return Ok(());
}
let zero = v.xyz_is_zero();
for r in &f.rules {
match r.key.as_str() {
"omitempty" => {
if zero {
break; }
continue;
}
"required" => {
if zero {
return Err(field_rule_error(f, "required"));
}
continue;
}
_ => {
if !v.xyz_rule_ok(r) {
return Err(field_rule_error(f, &r.key));
}
}
}
}
Ok(())
}
pub fn check_opt_rules<T: XyzField>(f: &FieldMeta, v: Option<&T>) -> errors::Result<()> {
if f.rules.is_empty() {
return Ok(());
}
let zero = v.is_none();
for r in &f.rules {
match r.key.as_str() {
"omitempty" => {
if zero {
break;
}
continue;
}
"required" => {
if zero {
return Err(field_rule_error(f, "required"));
}
continue;
}
_ => match v {
Some(inner) => {
if !inner.xyz_rule_ok(r) {
return Err(field_rule_error(f, &r.key));
}
}
None => {
return Err(field_rule_error(f, &r.key));
}
},
}
}
Ok(())
}
pub fn check_vec_rules<T: XyzField>(f: &FieldMeta, v: &[T]) -> errors::Result<()> {
if f.rules.is_empty() {
return Ok(());
}
let zero = v.is_empty();
let fmt = format!(
"[{}]",
v.iter().map(|x| x.xyz_fmt()).collect::<Vec<_>>().join(" ")
);
for r in &f.rules {
match r.key.as_str() {
"omitempty" => {
if zero {
break;
}
continue;
}
"required" => {
if zero {
return Err(field_rule_error(f, "required"));
}
continue;
}
"min" | "max" | "len" => {
let n = v.len() as f64;
let ok = match r.key.as_str() {
"min" => n >= r.num,
"max" => n <= r.num,
_ => n == r.num,
};
if !ok {
return Err(field_rule_error(f, &r.key));
}
}
"oneof" => {
if !r.args.iter().any(|a| a == &fmt) {
return Err(field_rule_error(f, &r.key));
}
}
_ => {
return Err(field_rule_error(f, &r.key));
}
}
}
Ok(())
}
pub fn elem_ref(f: &FieldMeta) -> Option<&FieldMeta> {
f.elem.as_deref()
}