use super::*;
pub(super) fn parse_mappings(template: &Value) -> Mappings {
let mut out: Mappings = BTreeMap::new();
let Some(maps) = template.get("Mappings").and_then(|v| v.as_object()) else {
return out;
};
for (map_name, top) in maps {
let Some(top_obj) = top.as_object() else {
continue;
};
let mut top_out = BTreeMap::new();
for (top_key, second) in top_obj {
let Some(second_obj) = second.as_object() else {
continue;
};
let mut second_out: BTreeMap<String, Value> = BTreeMap::new();
for (k, v) in second_obj {
second_out.insert(k.clone(), v.clone());
}
top_out.insert(top_key.clone(), second_out);
}
out.insert(map_name.clone(), top_out);
}
out
}
pub(super) fn apply_mappings(
value: &Value,
parameters: &BTreeMap<String, String>,
mappings: &Mappings,
conditions: &BTreeMap<String, bool>,
) -> Result<Value, String> {
match value {
Value::Object(map) => {
if let Some(arr) = map.get("Fn::If").and_then(|v| v.as_array()) {
if arr.len() == 3 {
let cond_name = arr[0].as_str().unwrap_or("");
if let Some(picked_idx) =
conditions
.get(cond_name)
.copied()
.map(|b| if b { 1 } else { 2 })
{
let mut new_arr = arr.clone();
new_arr[picked_idx] =
apply_mappings(&arr[picked_idx], parameters, mappings, conditions)?;
let mut rewritten = serde_json::Map::new();
rewritten.insert("Fn::If".to_string(), Value::Array(new_arr));
return Ok(Value::Object(rewritten));
}
}
}
if let Some(arr) = map.get("Fn::FindInMap").and_then(|v| v.as_array()) {
return resolve_find_in_map(arr, parameters, mappings, conditions);
}
let mut new_map = serde_json::Map::new();
for (k, v) in map {
new_map.insert(
k.clone(),
apply_mappings(v, parameters, mappings, conditions)?,
);
}
Ok(Value::Object(new_map))
}
Value::Array(arr) => {
let mut out = Vec::with_capacity(arr.len());
for v in arr {
out.push(apply_mappings(v, parameters, mappings, conditions)?);
}
Ok(Value::Array(out))
}
other => Ok(other.clone()),
}
}
pub(super) fn resolve_find_in_map(
arr: &[Value],
parameters: &BTreeMap<String, String>,
mappings: &Mappings,
conditions: &BTreeMap<String, bool>,
) -> Result<Value, String> {
if arr.len() != 3 && arr.len() != 4 {
return Err(format!(
"Fn::FindInMap requires 3 or 4 arguments, got {}",
arr.len()
));
}
let default_value: Option<Value> = if arr.len() == 4 {
let opts = arr[3].as_object().ok_or_else(|| {
"Fn::FindInMap fourth argument must be an object with a DefaultValue key".to_string()
})?;
let dv = opts.get("DefaultValue").ok_or_else(|| {
"Fn::FindInMap fourth argument must contain a DefaultValue key".to_string()
})?;
Some(apply_mappings(dv, parameters, mappings, conditions)?)
} else {
None
};
let map_name = stringify_findinmap_arg(&arr[0], parameters, mappings, conditions)?;
let top_key = stringify_findinmap_arg(&arr[1], parameters, mappings, conditions)?;
let second_key = stringify_findinmap_arg(&arr[2], parameters, mappings, conditions)?;
if let Some(top) = mappings.get(&map_name) {
if let Some(second) = top.get(&top_key) {
if let Some(leaf) = second.get(&second_key) {
return Ok(leaf.clone());
}
}
}
if let Some(dv) = default_value {
return Ok(dv);
}
Err(format!(
"Template error: Unable to get mapping for {map_name}::{top_key}::{second_key}"
))
}
pub(super) fn stringify_findinmap_arg(
value: &Value,
parameters: &BTreeMap<String, String>,
mappings: &Mappings,
conditions: &BTreeMap<String, bool>,
) -> Result<String, String> {
match value {
Value::String(s) => Ok(s.clone()),
Value::Object(m) => {
if let Some(name) = m.get("Ref").and_then(|v| v.as_str()) {
if let Some(p) = parameters.get(name) {
return Ok(p.clone());
}
if let Some(Value::String(s)) = pseudo_value(name, parameters) {
return Ok(s);
}
return Ok(name.to_string());
}
if let Some(arr) = m.get("Fn::FindInMap").and_then(|v| v.as_array()) {
let resolved = resolve_find_in_map(arr, parameters, mappings, conditions)?;
return Ok(match resolved {
Value::String(s) => s,
other => other.to_string(),
});
}
Ok(value.to_string())
}
_ => Ok(value.to_string()),
}
}