pub mod literals;
pub mod probes;
pub mod rename;
pub mod sort;
pub mod vmod_args;
pub fn normalize(
p: &mut crate::ast::Program,
specs: &std::collections::BTreeMap<String, crate::vmod::VmodSpec>,
rename: bool,
) -> crate::ast::Result<crate::ast::NameMap> {
literals::run(p);
vmod_args::run(p, specs)?;
probes::run(p);
let names = if rename {
crate::normalize::rename::run(p)
} else {
crate::normalize::rename::merge_only(p);
crate::ast::NameMap::default()
};
sort::run(p);
Ok(names)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::builder::*;
use crate::ast::{BinOp, FieldValue};
use crate::canon;
use crate::vmod::{ArgSpec, Sig, VmodSpec};
use std::collections::BTreeMap;
#[test]
fn n13_full_pipeline_all_five_passes() {
let fields_b = vec![
fexpr("host", str_("b.example.com")),
field(
"probe",
FieldValue::Probe(vec![
fexpr("url", str_("/health")),
fexpr("interval", num("2.50")),
]),
),
];
let fields_a = vec![fexpr("host", str_("a.example.com"))];
let vcl_recv_body = vec![
set(&["req", "http", "x"], var(&["web_a"])),
set(&["req", "http", "y"], var(&["web_b"])),
if_(
vec![(
bin(BinOp::Match, var(&["client", "ip"]), var(&["secure"])),
vec![],
)],
None,
),
expr_stmt(fcall(
&["std", "log"],
vec![arg(str_("hi")), narg("resolve", bool_(false))],
)),
];
let mut prog = program(vec![
sub("vcl_recv", vcl_recv_body),
backend("web_b", fields_b),
backend("web_a", fields_a),
acl("secure", vec![acl_entry("10.0.0.0", None, false)]),
]);
let mut funcs = BTreeMap::new();
funcs.insert(
"log".to_string(),
Sig {
args: vec![
ArgSpec {
name: Some("msg".to_string()),
default: None,
optional: false,
enum_values: None,
},
ArgSpec {
name: Some("resolve".to_string()),
default: Some("false".to_string()),
optional: true,
enum_values: None,
},
],
},
);
let mut specs = BTreeMap::new();
specs.insert(
"std".to_string(),
VmodSpec {
funcs,
objects: BTreeMap::new(),
},
);
let names = normalize(&mut prog, &specs, true).expect("normalize should succeed");
let backend_name = |orig: &str| -> String {
names
.entries
.iter()
.find(|e| e.0 == "backend" && e.2 == orig)
.unwrap_or_else(|| panic!("no rename entry for {orig}"))
.1
.clone()
};
assert_eq!(backend_name("web_a"), "backend_1");
assert_eq!(backend_name("web_b"), "backend_2");
let json = canon::to_json(&prog);
let decls = json["decls"].as_array().unwrap();
let kinds: Vec<&str> = decls.iter().map(|d| d["type"].as_str().unwrap()).collect();
assert_eq!(kinds, vec!["probe", "backend", "backend", "acl", "sub"]);
let probe = &decls[0];
assert_eq!(probe["name"], "probe_1");
let interval_field = probe["body"]
.as_array()
.unwrap()
.iter()
.find(|f| f["name"] == "interval")
.unwrap();
assert_eq!(interval_field["value"]["value"]["value"], "2.5");
let b2 = decls
.iter()
.find(|d| d["type"] == "backend" && d["name"] == "backend_2")
.unwrap();
let probe_field = b2["body"]
.as_array()
.unwrap()
.iter()
.find(|f| f["name"] == "probe")
.unwrap();
assert_eq!(probe_field["value"]["type"], "probe_ref");
assert_eq!(probe_field["value"]["value"], "probe_1");
let vcl_recv = decls.iter().find(|d| d["name"] == "vcl_recv").unwrap();
let body = vcl_recv["body"].as_array().unwrap();
let log_stmt = &body[3];
assert_eq!(log_stmt["type"], "expr");
let call_expr = &log_stmt["expr"];
assert_eq!(call_expr["type"], "call");
let call_args = call_expr["value"]["args"].as_array().unwrap();
assert_eq!(call_args.len(), 1);
assert_eq!(call_args[0]["name"], serde_json::Value::Null);
assert_eq!(call_args[0]["value"]["type"], "str");
assert_eq!(call_args[0]["value"]["value"], "hi");
}
}