concinnity_dev/command/
explain.rs1use crate::command::{provenance, resolve_world_path};
8
9pub fn explain(name: &str, json_path: Option<&str>) -> std::io::Result<()> {
12 let json_path = resolve_world_path(json_path)?;
13 let content = std::fs::read_to_string(&json_path)?;
14
15 let loaded = concinnity_cook::prepare_world(
16 &content,
17 concinnity_cook::paths::assets_dir().as_deref(),
18 crate::cook_platform(),
19 )
20 .map_err(|errs| concinnity_cook::check::report_validation_errors(&errs))?;
21
22 let Some(asset) = loaded.assets.iter().find(|a| a.name == name) else {
23 let mut close: Vec<&str> = loaded
24 .assets
25 .iter()
26 .map(|a| a.name.as_str())
27 .filter(|n| n.contains(name))
28 .take(5)
29 .collect();
30 close.sort_unstable();
31 let hint = if close.is_empty() {
32 String::new()
33 } else {
34 format!("; close matches: {}", close.join(", "))
35 };
36 return Err(std::io::Error::new(
37 std::io::ErrorKind::NotFound,
38 format!("no asset named '{}' in the expanded world{}", name, hint),
39 ));
40 };
41
42 let line = serde_json::json!({
43 "name": asset.name,
44 "type": asset.asset_type,
45 "args": asset.args,
46 });
47
48 println!("// {}", provenance(&loaded, &asset.name));
49 println!("{}", serde_json::to_string(&line)?);
50 Ok(())
51}
52
53#[cfg(test)]
54mod tests {
55 use super::*;
56
57 fn write_world(content: &str) -> (tempfile::TempDir, String) {
58 let dir = tempfile::tempdir().unwrap();
59 let path = dir.path().join("world.jsonl");
60 std::fs::write(&path, content).unwrap();
61 (dir, path.to_string_lossy().into_owned())
62 }
63
64 #[test]
65 fn explain_prints_a_known_asset() {
66 let (_dir, path) =
67 write_world("{\"name\":\"gfx\",\"type\":\"GraphicsConfig\",\"args\":{}}\n");
68 explain("gfx", Some(&path)).unwrap();
69 }
70
71 #[test]
72 fn explain_of_an_unknown_name_offers_close_matches() {
73 let (_dir, path) =
74 write_world("{\"name\":\"gfx\",\"type\":\"GraphicsConfig\",\"args\":{}}\n");
75 let err = explain("gf", Some(&path)).unwrap_err();
76 assert_eq!(err.kind(), std::io::ErrorKind::NotFound);
77 let msg = err.to_string();
78 assert!(msg.contains("close matches"), "got: {msg}");
79 assert!(msg.contains("gfx"), "got: {msg}");
80 }
81
82 #[test]
83 fn explain_of_an_unknown_name_without_matches_has_no_hint() {
84 let (_dir, path) =
85 write_world("{\"name\":\"gfx\",\"type\":\"GraphicsConfig\",\"args\":{}}\n");
86 let err = explain("zzz_nothing", Some(&path)).unwrap_err();
87 assert_eq!(err.kind(), std::io::ErrorKind::NotFound);
88 assert!(!err.to_string().contains("close matches"), "got: {err}");
89 }
90
91 #[test]
92 fn explain_surfaces_validation_failures() {
93 let (_dir, path) =
94 write_world("{\"name\":\"odd\",\"type\":\"NotARealAssetType\",\"args\":{}}\n");
95 assert!(explain("odd", Some(&path)).is_err());
96 }
97}