1use crate::ids::{string_id, validation_error};
7use thiserror::Error;
8
9const MAX_ACTOR_ID_BYTES: usize = 256;
10
11validation_error!(
12 ActorIdValidationError,
13 "invalid actor_id {value:?}: {reason}"
14);
15
16string_id! {
17 ActorId,
21 error = ActorIdValidationError,
22 validate = validate_actor_id,
23 schema(
24 description = "Stable opaque actor id containing 1 to 256 visible ASCII characters.",
25 pattern = r"^[\x21-\x7E]{1,256}$",
26 example = "usr_8f3c"
27 )
28}
29
30impl ActorId {
31 pub fn loonfs() -> Self {
33 Self::parse("loonfs").expect("`loonfs` should be a valid actor id")
34 }
35}
36
37fn validate_actor_id(value: &str) -> Result<(), ActorIdValidationError> {
38 if value.is_empty() {
39 return Err(ActorIdValidationError::new(value, "must not be empty"));
40 }
41 if value.len() > MAX_ACTOR_ID_BYTES {
42 return Err(ActorIdValidationError::new(
43 value,
44 format!("must be {MAX_ACTOR_ID_BYTES} bytes or fewer"),
45 ));
46 }
47 if !value.bytes().all(|byte| (0x21..=0x7e).contains(&byte)) {
48 return Err(ActorIdValidationError::new(
49 value,
50 "must contain only visible ASCII characters",
51 ));
52 }
53 Ok(())
54}
55
56#[cfg(test)]
57mod tests {
58 use super::ActorId;
59
60 #[test]
61 fn actor_id_rejects_invalid_values_with_stable_reasons() {
62 let too_long = "x".repeat(257);
63 for (value, reason) in [
64 ("", "must not be empty"),
65 (&too_long, "must be 256 bytes or fewer"),
66 (" actor", "must contain only visible ASCII characters"),
67 ("actor ", "must contain only visible ASCII characters"),
68 ("actor id", "must contain only visible ASCII characters"),
69 ("actor-雪", "must contain only visible ASCII characters"),
70 ("actor\nid", "must contain only visible ASCII characters"),
71 ("actor\0id", "must contain only visible ASCII characters"),
72 (
73 "actor\u{7f}id",
74 "must contain only visible ASCII characters",
75 ),
76 ] {
77 let error = ActorId::parse(value).expect_err("invalid actor id");
78 assert_eq!(error.value(), value);
79 assert_eq!(error.reason(), reason);
80 }
81 }
82
83 #[test]
84 fn actor_id_error_escapes_hostile_input() {
85 let error = ActorId::parse("actor\nid").expect_err("control character");
86
87 assert_eq!(
88 error.to_string(),
89 r#"invalid actor_id "actor\nid": must contain only visible ASCII characters"#
90 );
91 }
92
93 #[test]
94 fn actor_id_accepts_external_syntax_and_round_trips() {
95 let exactly_256_bytes = "x".repeat(256);
96 for value in [
97 "auth0|64abc",
98 "AAD:uPn@Example",
99 "123e4567-e89b-12d3-a456-426614174000",
100 &exactly_256_bytes,
101 ] {
102 let parsed = ActorId::parse(value).expect("valid external actor id");
103 assert_eq!(parsed.as_str(), value);
104 assert_eq!(parsed.to_string(), value);
105 assert_eq!(ActorId::try_from(value).expect("try_from actor id"), parsed);
106 assert_eq!(value.parse::<ActorId>().expect("from_str actor id"), parsed);
107
108 let json = serde_json::to_string(&parsed).expect("serialize actor id");
109 assert_eq!(
110 serde_json::from_str::<ActorId>(&json).expect("deserialize actor id"),
111 parsed
112 );
113 }
114 }
115}