Skip to main content

khive_gate/
request.rs

1use khive_types::Namespace;
2use serde::{Deserialize, Serialize};
3
4use crate::{ActorRef, GateContext, GateValidationError};
5
6// ---------- Request ----------
7
8/// What the gate sees on every verb invocation.
9///
10/// The JSON projection of this struct is the input shape policies receive
11/// (e.g. Rego's `input.actor`, `input.verb`, `input.args`). The shape is a
12/// public contract — changing field names is a breaking change.
13///
14/// Invariant: `verb` must be non-empty. `actor` is validated by [`ActorRef`].
15/// Enforced at construction and deserialization.
16#[derive(Clone, Debug, Serialize)]
17pub struct GateRequest {
18    pub actor: ActorRef,
19    pub namespace: Namespace,
20    pub verb: String,
21    pub args: serde_json::Value,
22    #[serde(default)]
23    pub context: GateContext,
24}
25
26/// Raw deserialization target for [`GateRequest`] — validated via `TryFrom`.
27#[derive(Deserialize)]
28struct RawGateRequest {
29    actor: ActorRef,
30    namespace: Namespace,
31    verb: String,
32    args: serde_json::Value,
33    #[serde(default)]
34    context: GateContext,
35}
36
37impl TryFrom<RawGateRequest> for GateRequest {
38    type Error = GateValidationError;
39
40    fn try_from(raw: RawGateRequest) -> Result<Self, Self::Error> {
41        if raw.verb.is_empty() {
42            return Err(GateValidationError::EmptyVerb);
43        }
44        Ok(Self {
45            actor: raw.actor,
46            namespace: raw.namespace,
47            verb: raw.verb,
48            args: raw.args,
49            context: raw.context,
50        })
51    }
52}
53
54impl<'de> Deserialize<'de> for GateRequest {
55    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
56    where
57        D: serde::Deserializer<'de>,
58    {
59        let raw = RawGateRequest::deserialize(deserializer)?;
60        GateRequest::try_from(raw).map_err(serde::de::Error::custom)
61    }
62}
63
64impl GateRequest {
65    /// Create a validated `GateRequest`. Returns `Err` if `verb` is empty.
66    pub fn try_new(
67        actor: ActorRef,
68        namespace: Namespace,
69        verb: impl Into<String>,
70        args: serde_json::Value,
71    ) -> Result<Self, GateValidationError> {
72        let verb = verb.into();
73        if verb.is_empty() {
74            return Err(GateValidationError::EmptyVerb);
75        }
76        Ok(Self {
77            actor,
78            namespace,
79            verb,
80            args,
81            context: GateContext::default(),
82        })
83    }
84
85    /// Builds a `GateRequest` with default (empty) context. Panics if `verb` is empty.
86    pub fn new(
87        actor: ActorRef,
88        namespace: Namespace,
89        verb: impl Into<String>,
90        args: serde_json::Value,
91    ) -> Self {
92        Self::try_new(actor, namespace, verb, args)
93            .expect("GateRequest::new: verb must not be empty")
94    }
95
96    /// Attaches a `GateContext` (session, timestamp, source) to this request.
97    pub fn with_context(mut self, context: GateContext) -> Self {
98        self.context = context;
99        self
100    }
101}