use super::expand::{ExpandReport, asset_name, type_norm};
pub(crate) fn apply_app_config(
assets: &mut [serde_json::Value],
report: &mut ExpandReport,
) -> Result<(), String> {
let mut app_name: Option<String> = None;
let mut first: Option<String> = None;
for v in assets.iter() {
if type_norm(v) != "appconfig" {
continue;
}
let name = asset_name(v);
if let Some(prev) = &first {
return Err(format!(
"AppConfig '{}': the world already declares AppConfig '{}'; \
declare at most one",
name, prev
));
}
first = Some(name);
app_name = v
.get("args")
.and_then(|a| a.get("name"))
.and_then(|n| n.as_str())
.map(str::to_string)
.filter(|s| !s.is_empty());
}
let Some(app_name) = app_name else {
return Ok(());
};
for v in assets.iter_mut() {
if type_norm(v) != "window" {
continue;
}
let name = asset_name(v);
{
let args = v
.get_mut("args")
.and_then(|a| a.as_object_mut())
.ok_or_else(|| format!("Window '{}': args must be an object", name))?;
if args.contains_key("title") {
continue;
}
args.insert(
"title".to_string(),
serde_json::Value::String(app_name.clone()),
);
}
if let Some(entry) = report
.injected
.iter_mut()
.find(|i| i.name == name && i.asset_type == "Window")
{
entry.args = v
.get("args")
.cloned()
.unwrap_or_else(|| serde_json::json!({}));
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn app(name: &str) -> serde_json::Value {
serde_json::json!({"name":"app","type":"AppConfig","args":{"name": name}})
}
fn window(args: serde_json::Value) -> serde_json::Value {
serde_json::json!({"name":"w","type":"Window","args": args})
}
fn title_of(assets: &[serde_json::Value]) -> Option<String> {
assets
.iter()
.find(|v| type_norm(v) == "window")
.and_then(|v| v.get("args"))
.and_then(|a| a.get("title"))
.and_then(|t| t.as_str())
.map(str::to_string)
}
#[test]
fn name_fills_an_untitled_window() {
let mut assets = vec![app("My Game"), window(serde_json::json!({}))];
let mut report = ExpandReport::default();
apply_app_config(&mut assets, &mut report).unwrap();
assert_eq!(title_of(&assets).as_deref(), Some("My Game"));
}
#[test]
fn authored_title_is_not_overridden() {
let mut assets = vec![app("My Game"), window(serde_json::json!({"title": "Kept"}))];
let mut report = ExpandReport::default();
apply_app_config(&mut assets, &mut report).unwrap();
assert_eq!(title_of(&assets).as_deref(), Some("Kept"));
}
#[test]
fn no_app_config_leaves_the_window_untitled() {
let mut assets = vec![window(serde_json::json!({}))];
let mut report = ExpandReport::default();
apply_app_config(&mut assets, &mut report).unwrap();
assert_eq!(title_of(&assets), None);
}
#[test]
fn empty_app_config_name_fills_nothing() {
let mut assets = vec![app(""), window(serde_json::json!({}))];
let mut report = ExpandReport::default();
apply_app_config(&mut assets, &mut report).unwrap();
assert_eq!(title_of(&assets), None);
}
#[test]
fn duplicate_app_config_is_an_error() {
let mut assets = vec![
serde_json::json!({"name":"a","type":"AppConfig","args":{"name":"A"}}),
serde_json::json!({"name":"b","type":"AppConfig","args":{"name":"B"}}),
];
let mut report = ExpandReport::default();
let err = apply_app_config(&mut assets, &mut report).unwrap_err();
assert!(err.contains("at most one"));
}
#[test]
fn non_object_window_args_are_an_error() {
let mut assets = vec![app("My Game"), window(serde_json::json!("wide"))];
let mut report = ExpandReport::default();
let err = apply_app_config(&mut assets, &mut report).unwrap_err();
assert!(err.contains("Window 'w'"), "{err}");
assert!(err.contains("args must be an object"), "{err}");
}
#[test]
fn non_window_assets_are_untouched() {
let mut assets = vec![
app("My Game"),
serde_json::json!({"name":"gfx","type":"GraphicsConfig","args":{}}),
];
let mut report = ExpandReport::default();
apply_app_config(&mut assets, &mut report).unwrap();
assert!(assets[1]["args"].get("title").is_none());
}
#[test]
fn injected_window_args_are_synced_in_the_report() {
let mut assets = vec![app("My Game"), window(serde_json::json!({}))];
let mut report = ExpandReport::default();
report.record("w", "Window", serde_json::json!({}), "companion");
apply_app_config(&mut assets, &mut report).unwrap();
let entry = report
.injected
.iter()
.find(|i| i.name == "w" && i.asset_type == "Window")
.unwrap();
assert_eq!(entry.args["title"], serde_json::json!("My Game"));
}
}