Skip to main content

khive_gate/
actor.rs

1use serde::{Deserialize, Serialize};
2
3use crate::GateValidationError;
4
5/// Kind prefixes reserved by runtime event attribution and its identity fixtures.
6///
7/// Add newly stamped kinds here and extend the per-kind event scope regressions.
8/// This is not a closed taxonomy: [`ActorRef::try_new`] accepts any non-empty kind.
9pub const RUNTIME_STAMPED_ACTOR_KINDS: &[&str] = &["actor", "anonymous", "agent"];
10
11/// Caller identity with non-empty `kind` and `id`, validated on construction and deserialization.
12///
13/// See `crates/khive-gate/docs/api/policy-types.md`.
14#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)]
15pub struct ActorRef {
16    pub kind: String,
17    pub id: String,
18}
19
20/// Raw deserialization target for [`ActorRef`] — validated via `TryFrom`.
21#[derive(Deserialize)]
22struct RawActorRef {
23    kind: String,
24    id: String,
25}
26
27impl TryFrom<RawActorRef> for ActorRef {
28    type Error = GateValidationError;
29
30    fn try_from(raw: RawActorRef) -> Result<Self, Self::Error> {
31        Self::try_new(raw.kind, raw.id)
32    }
33}
34
35impl<'de> Deserialize<'de> for ActorRef {
36    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
37    where
38        D: serde::Deserializer<'de>,
39    {
40        let raw = RawActorRef::deserialize(deserializer)?;
41        ActorRef::try_from(raw).map_err(serde::de::Error::custom)
42    }
43}
44
45impl ActorRef {
46    /// Create a validated `ActorRef`. Returns `Err` if `kind` or `id` is empty.
47    pub fn try_new(
48        kind: impl Into<String>,
49        id: impl Into<String>,
50    ) -> Result<Self, GateValidationError> {
51        let kind = kind.into();
52        let id = id.into();
53        if kind.is_empty() {
54            return Err(GateValidationError::EmptyActorKind);
55        }
56        if id.is_empty() {
57            return Err(GateValidationError::EmptyActorId);
58        }
59        Ok(Self { kind, id })
60    }
61
62    /// Create a validated `ActorRef`. Panics if `kind` or `id` is empty.
63    pub fn new(kind: impl Into<String>, id: impl Into<String>) -> Self {
64        Self::try_new(kind, id).expect("ActorRef::new: kind and id must not be empty")
65    }
66
67    /// The implicit caller for unauthenticated local usage.
68    pub fn anonymous() -> Self {
69        Self {
70            kind: "anonymous".into(),
71            id: "local".into(),
72        }
73    }
74
75    /// Whether this actor is the implicit anonymous caller.
76    pub fn is_anonymous(&self) -> bool {
77        self.kind == "anonymous"
78    }
79
80    /// Return the explicit binding ID, or `None` for the anonymous caller.
81    ///
82    /// Anonymous identity must never participate in binding resolution. See
83    /// `crates/khive-gate/docs/api/policy-types.md`.
84    pub fn binding_id(&self) -> Option<&str> {
85        if self.is_anonymous() {
86            None
87        } else {
88            Some(self.id.as_str())
89        }
90    }
91}