Skip to main content

bern_protocol/
resource.rs

1//! Names of the resource files an application ships with its build.
2//!
3//! A resource name is the file's `/`-separated path relative to `Bern.toml`,
4//! for example `media/aurora.mp4`. The build tool checks the declared names
5//! with this rule and the engine host checks the names an engine resolves,
6//! so both sides agree on what a name is.
7
8/// Check a resource name. Every segment uses ASCII letters, digits, `-`,
9/// `_` and `.`, and does not start with `.`; absolute paths, empty
10/// segments, `..` and backslashes are rejected. The error explains why.
11pub fn check_resource_name(name: &str) -> Result<(), String> {
12    if name.is_empty() {
13        return Err("a resource name cannot be empty".into());
14    }
15    if name.starts_with('/') {
16        return Err(format!(
17            "resource `{name}` is absolute; name it relative to Bern.toml"
18        ));
19    }
20    for segment in name.split('/') {
21        if segment.is_empty() {
22            return Err(format!("resource `{name}` has an empty path segment"));
23        }
24        if segment.starts_with('.') {
25            return Err(format!(
26                "resource `{name}`: `{segment}` starts with `.`; hidden files, `.` and `..` are not resources"
27            ));
28        }
29        if let Some(invalid) = segment
30            .chars()
31            .find(|c| !(c.is_ascii_alphanumeric() || matches!(c, '-' | '_' | '.')))
32        {
33            return Err(format!(
34                "resource `{name}` contains `{invalid}`; names use ASCII letters, digits, `-`, `_` and `.`"
35            ));
36        }
37    }
38    Ok(())
39}
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44
45    #[test]
46    fn names_are_portable_relative_paths() {
47        for name in ["media/aurora.mp4", "seed.json", "a/b-c/d_e.f.txt", "x"] {
48            assert_eq!(check_resource_name(name), Ok(()), "{name}");
49        }
50        for name in [
51            "",
52            "/media/a.mp4",
53            "media//a.mp4",
54            "media/",
55            "../a.mp4",
56            "media/./a.mp4",
57            ".hidden",
58            "media\\a.mp4",
59            "media/a b.mp4",
60            "影片.mp4",
61        ] {
62            assert!(check_resource_name(name).is_err(), "{name}");
63        }
64    }
65}