Skip to main content

khive_gate/
actor.rs

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