1#[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 pub fn name(&self) -> &str {
19 &self.name
20 }
21
22 pub fn image_name(&self) -> String {
24 format!("spud-{}", self.name)
25 }
26
27 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}