Skip to main content

spudkit_core/
spud.rs

1/// A spud — a named app that maps to a `spud-{name}` Docker image.
2#[derive(Clone, Debug)]
3pub struct Spud {
4    name: String,
5}
6
7impl Spud {
8    pub fn new(name: &str) -> anyhow::Result<Self> {
9        if name.is_empty() || name.contains('/') || name.contains("..") {
10            anyhow::bail!("invalid spud name: {name:?}");
11        }
12        Ok(Self {
13            name: name.to_string(),
14        })
15    }
16
17    /// The short display name (e.g., "hello-world").
18    pub fn name(&self) -> &str {
19        &self.name
20    }
21
22    /// The Docker image name (e.g., "spud-hello-world").
23    pub fn image_name(&self) -> String {
24        format!("spud-{}", self.name)
25    }
26
27    /// The Unix socket path for this app.
28    pub fn socket_path(&self) -> String {
29        format!("/tmp/spudkit-{}.sock", self.name)
30    }
31}
32
33#[cfg(test)]
34mod tests {
35    use super::*;
36    use rstest::rstest;
37
38    #[test]
39    fn image_name_has_prefix() {
40        let spud = Spud::new("hello-world").unwrap();
41        assert_eq!(spud.image_name(), "spud-hello-world");
42    }
43
44    #[test]
45    fn socket_path_uses_name() {
46        let spud = Spud::new("hello-world").unwrap();
47        assert_eq!(spud.socket_path(), "/tmp/spudkit-hello-world.sock");
48    }
49
50    #[test]
51    fn name_returns_short_name() {
52        let spud = Spud::new("hello-world").unwrap();
53        assert_eq!(spud.name(), "hello-world");
54    }
55
56    #[rstest]
57    #[case::empty("")]
58    #[case::slash("foo/bar")]
59    #[case::dotdot("..")]
60    #[case::traversal("../../etc")]
61    fn rejects_invalid_names(#[case] name: &str) {
62        assert!(Spud::new(name).is_err(), "should reject: {name:?}");
63    }
64}