Skip to main content

only_engine/
interpolate.rs

1use std::collections::HashMap;
2
3use crate::{EngineError, PlanParam};
4
5/// Renders one command string by replacing semantic interpolation placeholders.
6///
7/// Supports `\{\{` and `\}\}` escape sequences to produce literal `{{` and `}}`.
8///
9/// Args:
10/// command: Raw command text from the execution plan.
11/// params: Bound plan parameters available to interpolation.
12///
13/// Returns:
14/// Rendered command text or an engine error when interpolation is invalid.
15pub fn interpolate(command: &str, params: &[PlanParam]) -> Result<String, EngineError> {
16    let parameter_map = params
17        .iter()
18        .filter_map(|param| {
19            param
20                .value
21                .as_ref()
22                .or(param.default_value.as_ref())
23                .map(|value| (param.name.as_str(), value.as_str()))
24        })
25        .collect::<HashMap<_, _>>();
26    let mut output = String::with_capacity(command.len());
27    let mut rest = command;
28
29    while let Some(start) = rest.find("{{") {
30        push_literal(&mut output, &rest[..start]);
31
32        if marker_is_escaped(rest, start) {
33            output.pop();
34            output.push_str("{{");
35            rest = &rest[start + 2..];
36            continue;
37        }
38
39        let placeholder = &rest[start + 2..];
40        let Some(end) = placeholder.find("}}") else {
41            return Err(EngineError::Interpolation(
42                "missing `}}` in command".to_string(),
43            ));
44        };
45
46        let name = placeholder[..end].trim();
47        let Some(value) = parameter_map.get(name) else {
48            return Err(EngineError::Interpolation(format!(
49                "variable '{{{{{name}}}}}' is not defined"
50            )));
51        };
52
53        output.push_str(value);
54        rest = &placeholder[end + 2..];
55    }
56
57    push_literal(&mut output, rest);
58    Ok(output)
59}
60
61fn push_literal(output: &mut String, segment: &str) {
62    let mut offset = 0usize;
63
64    while let Some(rel) = segment[offset..].find("}}") {
65        let close = offset + rel;
66        output.push_str(&segment[offset..close]);
67        if marker_is_escaped(segment, close) {
68            output.pop();
69        }
70        output.push_str("}}");
71        offset = close + 2;
72    }
73
74    output.push_str(&segment[offset..]);
75}
76
77fn marker_is_escaped(text: &str, marker_start: usize) -> bool {
78    let mut slash_count = 0usize;
79    let bytes = text.as_bytes();
80    let mut index = marker_start;
81
82    while index > 0 && bytes[index - 1] == b'\\' {
83        slash_count += 1;
84        index -= 1;
85    }
86
87    slash_count % 2 == 1
88}