Skip to main content

khive_gate/
request.rs

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