Skip to main content

loonfs_api/
actor.rs

1//! Types for identifying who made a commit.
2//!
3//! Use a stable ID such as `usr_8f3c`, rather than an email address or display
4//! name. Profile changes should not change the actor recorded in file history.
5
6use 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    /// A validated actor identifier supplied by the application.
18    ///
19    /// Actor IDs contain 1 to 256 visible ASCII characters (0x21 through 0x7E).
20    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    /// Returns the id recorded when LoonFS creates a namespace root.
32    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}