Skip to main content

concinnity_dev/authoring/
rm.rs

1// src/rm.rs
2// Remove an asset from a world JSONL by its unique `name` field and rebuild.
3
4use crate::world::{WORLD_JSONL, known_names, patch_world_jsonl};
5use concinnity_cook::build_from_path;
6
7/// Remove the asset named `name` from `world_path` and rebuild.
8///
9/// Errors if `name` is not present. When it isn't, the error message includes
10/// the known asset names from the world so the caller can suggest a fix.
11pub fn rm_at_path(world_path: &str, name: &str) -> std::io::Result<()> {
12    let mut removed = false;
13
14    patch_world_jsonl(world_path, |assets| {
15        if let Some(i) = assets
16            .iter()
17            .position(|a| a.get("name").and_then(|v| v.as_str()) == Some(name))
18        {
19            let asset = assets.remove(i);
20            tracing::info!(
21                "Removed '{}' (type: {})",
22                name,
23                asset.get("type").and_then(|v| v.as_str()).unwrap_or("?"),
24            );
25            removed = true;
26        }
27    })?;
28
29    if !removed {
30        let known = known_names(world_path).unwrap_or_default();
31        if known.is_empty() {
32            return Err(std::io::Error::new(
33                std::io::ErrorKind::InvalidInput,
34                format!(
35                    "no asset named '{}' in {} (no assets declared)",
36                    name, WORLD_JSONL
37                ),
38            ));
39        }
40        return Err(std::io::Error::new(
41            std::io::ErrorKind::InvalidInput,
42            format!(
43                "no asset named '{}' in {}\nKnown names: {}",
44                name,
45                WORLD_JSONL,
46                known.join(", ")
47            ),
48        ));
49    }
50
51    build_from_path(world_path, crate::cook_platform())
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57
58    // Removal failures surface before any rebuild runs, so these tests never
59    // touch the compile pipeline.
60
61    #[test]
62    fn rm_of_a_missing_world_file_errors() {
63        let dir = tempfile::tempdir().unwrap();
64        let path = dir.path().join("missing.jsonl");
65        assert!(rm_at_path(path.to_str().unwrap(), "anything").is_err());
66    }
67
68    #[test]
69    fn rm_of_an_unknown_name_lists_the_known_names() {
70        let dir = tempfile::tempdir().unwrap();
71        let path = dir.path().join("world.jsonl");
72        std::fs::write(
73            &path,
74            concat!(
75                "{\"name\":\"log\",\"type\":\"Logger\",\"args\":{}}\n",
76                "{\"name\":\"log2\",\"type\":\"Logger\",\"args\":{}}\n",
77            ),
78        )
79        .unwrap();
80
81        let err = rm_at_path(path.to_str().unwrap(), "ghost").unwrap_err();
82        assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
83        let msg = err.to_string();
84        assert!(msg.contains("no asset named 'ghost'"), "got: {msg}");
85        assert!(msg.contains("Known names: log, log2"), "got: {msg}");
86        // The world file itself is left intact.
87        let survived = std::fs::read_to_string(&path).unwrap();
88        assert!(survived.contains("\"log\""));
89        assert!(survived.contains("\"log2\""));
90    }
91
92    #[test]
93    fn rm_from_an_empty_world_reports_no_assets() {
94        let dir = tempfile::tempdir().unwrap();
95        let path = dir.path().join("world.jsonl");
96        std::fs::write(&path, "").unwrap();
97
98        let err = rm_at_path(path.to_str().unwrap(), "ghost").unwrap_err();
99        assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
100        assert!(err.to_string().contains("no assets declared"), "got: {err}");
101    }
102}