Skip to main content

little_durable_objects/
actor_state.rs

1use serde::{Deserialize, Serialize};
2use std::fmt;
3
4use anyhow::{Result, ensure};
5
6#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
7pub struct ActorStorageKey(String);
8
9impl ActorStorageKey {
10    pub fn new<S>(id: S) -> Self
11    where
12        S: Into<String>,
13    {
14        Self(id.into())
15    }
16
17    pub fn as_str(&self) -> &str {
18        &self.0
19    }
20
21    pub fn validate(&self) -> Result<()> {
22        ensure!(!self.0.is_empty(), "actor storage key must not be empty");
23        ensure!(
24            self.0.len() <= 255,
25            "actor storage key must be at most 255 bytes"
26        );
27        ensure!(
28            self.0 != "." && self.0 != "..",
29            "actor storage key must not be a relative path component"
30        );
31        ensure!(
32            !self
33                .0
34                .chars()
35                .any(|character| character == '/' || character == '\\' || character.is_control()),
36            "actor storage key must not contain path separators or control characters"
37        );
38
39        Ok(())
40    }
41}
42
43impl fmt::Display for ActorStorageKey {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        f.write_str(&self.0)
46    }
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52
53    #[test]
54    fn accepts_a_single_safe_storage_component() {
55        ActorStorageKey::new("tenant_1.session-123")
56            .validate()
57            .expect("valid actor storage key");
58    }
59
60    #[test]
61    fn rejects_ids_that_can_escape_or_reshape_storage_paths() {
62        for id in [
63            "",
64            ".",
65            "..",
66            "../other",
67            "nested/object",
68            "windows\\path",
69            "bad\0id",
70        ] {
71            ActorStorageKey::new(id)
72                .validate()
73                .expect_err("unsafe actor storage key");
74        }
75    }
76}