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