use isoloom_spec::model::Spec;
use isoloom_spec::syntax::placeholder::{self, Place, PortField, Reference};
use crate::Environment;
use crate::addressing;
use crate::error::{Error, Result};
#[derive(Debug, Clone, Copy)]
pub struct Context<'a> {
spec: &'a Spec,
machine: &'a str,
}
impl<'a> Context<'a> {
#[must_use]
pub fn new(env: &'a Environment, machine: &'a str) -> Context<'a> {
Context { spec: env.spec(), machine }
}
#[must_use]
pub fn value(&self, r: &Reference) -> Option<String> {
match r {
Reference::MachineName => Some(self.machine.to_owned()),
Reference::OwnAddress { network } => self.address(self.machine, network),
Reference::Address { machine, network } => self.address(machine, network),
Reference::Port {
machine,
port,
field: PortField::Port,
} => {
let m = self.spec.machines.get(machine)?;
let found = m.ports.iter().find(|p| p.name.as_deref() == Some(port.as_str()))?;
Some(found.port.to_string())
}
_ => None,
}
}
fn address(&self, machine: &str, network: &str) -> Option<String> {
let octet = self.spec.machines.get(machine)?.networks.get(network)?;
Some(addressing::address(self.spec, network, *octet).to_string())
}
}
pub fn fill(text: &str, context: &Context<'_>) -> Result<String> {
let parts = placeholder::read(text, Place::Template).map_err(|wrong| {
wrong
.into_iter()
.next()
.map_or(Error::Placeholder(isoloom_spec::Error::Unclosed), Error::Placeholder)
})?;
placeholder::join(parts, |r| context.value(r).ok_or_else(|| Error::Unfilled(r.clone())))
}
#[cfg(test)]
mod tests {
use isoloom_spec::model::Target;
use super::*;
use crate::testing;
const LAB: &str = "version: 1\nname: t\nnetworks: { lab: { cidr: 10.9.0.0/24 } }\nmachines:\n web: { os: debian@12, networks: { lab: 10 }, command: x, ports: { site: 8080 } }\n db: { os: debian@12, networks: { lab: 20 }, command: y, ports: { pg: 5432 } }\noverrides:\n docker: { networks: { lab: { cidr: 10.70.0.0/24 } } }\n";
#[test]
fn a_template_is_filled_in_with_the_targets_values() {
let text = "{{ machine.name }} {{ machine.addresses.lab }} -> {{ machines.db.addresses.lab }}:{{ machines.db.ports.pg.port }}\n";
let vagrant = testing::environment(LAB, Target::Vagrant);
assert_eq!(
fill(text, &Context::new(&vagrant, "web")).ok().as_deref(),
Some("web 10.9.0.10 -> 10.9.0.20:5432\n")
);
let docker = testing::environment(LAB, Target::Docker);
assert_eq!(
fill(text, &Context::new(&docker, "web")).ok().as_deref(),
Some("web 10.70.0.10 -> 10.70.0.20:5432\n")
);
}
#[test]
fn a_placeholder_naming_nothing_or_a_run_time_value_is_reported() {
let spec = testing::environment(LAB, Target::Vagrant);
let wrong = fill(
"{{ machines.db.ports.nope.port }} {{ machines.nope.addresses.lab }}",
&Context::new(&spec, "web"),
)
.unwrap_err();
assert_eq!(wrong.to_string(), "`{{ machines.db.ports.nope.port }}` names nothing this lab has");
let run_time = fill("{{ machines.web.ports.site.published }}", &Context::new(&spec, "web")).unwrap_err();
assert!(matches!(run_time, Error::Placeholder(isoloom_spec::Error::NotHere { .. })), "{run_time:?}");
assert_eq!(fill("no placeholder {", &Context::new(&spec, "web")).ok().as_deref(), Some("no placeholder {"));
}
}