little_durable_objects/
actor_state.rs1use 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}