pub(crate) struct FieldListSpec {
pub(crate) prefix: &'static str,
pub(crate) label: &'static str,
pub(crate) example: &'static str,
}
pub(crate) fn parse_field_list(raw: &str, spec: &FieldListSpec) -> Result<Vec<String>, String> {
let label = spec.label;
let Some(inner) = raw
.strip_prefix(spec.prefix)
.and_then(|value| value.strip_suffix(')'))
else {
return Err(format!("unsupported {label} `{raw}`"));
};
let Some(list) = inner
.trim()
.strip_prefix('[')
.and_then(|value| value.strip_suffix(']'))
else {
return Err(format!(
"{label} `{raw}` must list fields as `{}`",
spec.example
));
};
let mut fields = Vec::new();
for part in list.split(',').map(str::trim) {
if part.is_empty() {
continue;
}
if !is_valid_field_name(part) {
return Err(format!("{label} `{raw}` lists invalid field name `{part}`"));
}
if fields.contains(&part.to_owned()) {
return Err(format!(
"{label} `{raw}` lists field `{part}` more than once"
));
}
fields.push(part.to_owned());
}
Ok(fields)
}
pub(super) fn is_valid_field_name(value: &str) -> bool {
let mut chars = value.chars();
matches!(chars.next(), Some(first) if first.is_ascii_alphabetic() || first == '_')
&& chars.all(|ch| ch.is_ascii_alphanumeric() || ch == '_')
}