Skip to main content

dpm_model/
actor.rs

1use serde::{Deserialize, Serialize};
2#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
3/// Principal category recorded in an audit operation.
4pub enum ActorKind {
5    /// A person.
6    Human,
7    /// An automated worker.
8    Agent,
9    /// An external service or CI verifier.
10    Service,
11}
12
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14/// Local principal identity; kind and name jointly distinguish actors.
15#[serde(deny_unknown_fields)]
16pub struct ActorId {
17    /// Domain category of this value.
18    pub kind: ActorKind,
19    /// Non-empty local name.
20    pub name: String,
21}
22
23impl ActorId {
24    /// Create a human principal.
25    pub fn human(name: impl Into<String>) -> Self {
26        Self {
27            kind: ActorKind::Human,
28            name: name.into(),
29        }
30    }
31
32    /// Create an agent principal.
33    pub fn agent(name: impl Into<String>) -> Self {
34        Self {
35            kind: ActorKind::Agent,
36            name: name.into(),
37        }
38    }
39
40    /// Create a service principal.
41    pub fn service(name: impl Into<String>) -> Self {
42        Self {
43            kind: ActorKind::Service,
44            name: name.into(),
45        }
46    }
47}
48
49/// The lowercase word a principal's spelling starts with, also used in prose.
50impl std::fmt::Display for ActorKind {
51    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52        f.write_str(match self {
53            Self::Human => "human",
54            Self::Agent => "agent",
55            Self::Service => "service",
56        })
57    }
58}
59
60impl std::fmt::Display for ActorId {
61    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62        write!(f, "{}:{}", self.kind, self.name)
63    }
64}
65
66/// A principal spelled other than `human:NAME`, `agent:NAME` or `service:NAME`.
67#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
68pub enum ActorParseError {
69    /// No `KIND:` prefix.
70    #[error("actor {0:?} must be human:NAME, agent:NAME, or service:NAME")]
71    MissingKind(String),
72    /// The kind is not one of the three principal categories.
73    #[error("unknown actor kind {0}")]
74    UnknownKind(String),
75    /// The name after the kind is blank.
76    #[error("actor name must not be empty")]
77    EmptyName,
78}
79
80impl std::str::FromStr for ActorId {
81    type Err = ActorParseError;
82
83    /// Parse the `KIND:NAME` spelling every adapter accepts, so CLI flags and tool arguments name
84    /// the same principal.
85    fn from_str(value: &str) -> Result<Self, Self::Err> {
86        let (kind, name) = value
87            .split_once(':')
88            .ok_or_else(|| ActorParseError::MissingKind(value.into()))?;
89        if name.trim().is_empty() {
90            return Err(ActorParseError::EmptyName);
91        }
92        let kind = match kind {
93            "human" => ActorKind::Human,
94            "agent" => ActorKind::Agent,
95            "service" => ActorKind::Service,
96            _ => return Err(ActorParseError::UnknownKind(kind.into())),
97        };
98        Ok(Self {
99            kind,
100            name: name.into(),
101        })
102    }
103}