use super::super::kdl::*;
use super::node_line;
use ::kdl::KdlNode;
use anyhow::{anyhow, bail};
pub(super) fn parse_matcher(
node: &KdlNode,
src: &str,
name: &str,
) -> anyhow::Result<crate::config::MatcherConfig> {
let line = node_line(src, node);
let predicates = parse_match_predicate_list(node, src, name)?;
if predicates.is_empty() {
bail!(
"{name}:{line}: empty `match {{ }}` block; remove it or \
add at least one predicate"
);
}
Ok(crate::config::MatcherConfig { predicates })
}
fn parse_match_predicate_list(
parent: &KdlNode,
src: &str,
name: &str,
) -> anyhow::Result<Vec<crate::config::MatchPredicateConfig>> {
let children = parent.children().map(|d| d.nodes()).unwrap_or_default();
let mut predicates = Vec::new();
for child in children {
predicates.push(parse_match_predicate(child, src, name)?);
}
Ok(predicates)
}
fn parse_match_predicate(
child: &KdlNode,
src: &str,
name: &str,
) -> anyhow::Result<crate::config::MatchPredicateConfig> {
use crate::config::MatchPredicateConfig;
let cline = node_line(src, child);
match child.name().value() {
"method" => {
let methods: Vec<String> = child
.entries()
.iter()
.filter(|e| e.name().is_none())
.filter_map(|e| e.value().as_string().map(str::to_owned))
.collect();
if methods.is_empty() {
bail!(
"{name}:{cline}: match `method` requires at \
least one method name"
);
}
for m in &methods {
hyper::Method::from_bytes(m.as_bytes())
.map_err(|e| {
anyhow!(
"{name}:{cline}: match invalid method \
{m:?}: {e}"
)
})?;
}
Ok(MatchPredicateConfig::Method(methods))
}
"header" => {
let header = req_arg_str(child, 0)?;
hyper::header::HeaderName::from_bytes(header.as_bytes())
.map_err(|e| {
anyhow!(
"{name}:{cline}: match invalid header name \
{header:?}: {e}"
)
})?;
let values: Vec<String> = child
.entries()
.iter()
.filter(|e| e.name().is_none())
.skip(1)
.filter_map(|e| e.value().as_string().map(str::to_owned))
.collect();
if values.is_empty() {
bail!(
"{name}:{cline}: match `header {header:?}` \
requires at least one value (use \
`header-absent` to match a missing header)"
);
}
for v in &values {
if let Some(re) = v.strip_prefix('~') {
regex::Regex::new(re).map_err(|e| {
anyhow!(
"{name}:{cline}: match invalid regex \
in `header {header:?}`: {e}"
)
})?;
}
}
Ok(MatchPredicateConfig::Header {
name: header,
values,
})
}
"header-absent" => {
let header = req_arg_str(child, 0)?;
hyper::header::HeaderName::from_bytes(header.as_bytes())
.map_err(|e| {
anyhow!(
"{name}:{cline}: match invalid header name \
{header:?}: {e}"
)
})?;
Ok(MatchPredicateConfig::HeaderAbsent { name: header })
}
"query" => {
let qname = req_arg_str(child, 0)?;
let values: Vec<String> = child
.entries()
.iter()
.filter(|e| e.name().is_none())
.skip(1)
.filter_map(|e| e.value().as_string().map(str::to_owned))
.collect();
if values.is_empty() {
bail!(
"{name}:{cline}: match `query {qname:?}` \
requires at least one value"
);
}
Ok(MatchPredicateConfig::Query {
name: qname,
values,
})
}
"path" => {
let patterns: Vec<String> = child
.entries()
.iter()
.filter(|e| e.name().is_none())
.filter_map(|e| e.value().as_string().map(str::to_owned))
.collect();
if patterns.is_empty() {
bail!(
"{name}:{cline}: match `path` requires at least \
one regex"
);
}
for p in &patterns {
regex::Regex::new(p).map_err(|e| {
anyhow!(
"{name}:{cline}: match invalid `path` \
regex {p:?}: {e}"
)
})?;
}
Ok(MatchPredicateConfig::Path(patterns))
}
"not" => {
let inner = parse_match_predicate_list(child, src, name)?;
if inner.is_empty() {
bail!(
"{name}:{cline}: empty `not {{ }}` block; \
remove it or add at least one inner predicate"
);
}
Ok(MatchPredicateConfig::Not(inner))
}
other => bail!(
"{name}:{cline}: unknown match predicate {other:?}; \
expected method, header, header-absent, query, path, \
or not"
),
}
}