Skip to main content

khive_gate/
actor.rs

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