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/// `args` contains submitted arguments at the dispatch boundary, before handler
11/// canonicalization and kind hooks. A gate may inspect these values, but must
12/// not treat them as the effective values a handler will execute. Policy that
13/// requires effective values belongs in the handler after its normalization.
14#[derive(Clone, Debug, Serialize)]
15pub struct GateRequest {
16    pub actor: ActorRef,
17    pub namespace: Namespace,
18    pub verb: String,
19    pub args: serde_json::Value,
20    #[serde(default)]
21    pub context: GateContext,
22}
23
24/// Raw deserialization target for [`GateRequest`] — validated via `TryFrom`.
25#[derive(Deserialize)]
26struct RawGateRequest {
27    actor: ActorRef,
28    namespace: Namespace,
29    verb: String,
30    args: serde_json::Value,
31    #[serde(default)]
32    context: GateContext,
33}
34
35impl TryFrom<RawGateRequest> for GateRequest {
36    type Error = GateValidationError;
37
38    fn try_from(raw: RawGateRequest) -> Result<Self, Self::Error> {
39        if raw.verb.is_empty() {
40            return Err(GateValidationError::EmptyVerb);
41        }
42        Ok(Self {
43            actor: raw.actor,
44            namespace: raw.namespace,
45            verb: raw.verb,
46            args: raw.args,
47            context: raw.context,
48        })
49    }
50}
51
52impl<'de> Deserialize<'de> for GateRequest {
53    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
54    where
55        D: serde::Deserializer<'de>,
56    {
57        let raw = RawGateRequest::deserialize(deserializer)?;
58        GateRequest::try_from(raw).map_err(serde::de::Error::custom)
59    }
60}
61
62impl GateRequest {
63    /// Create a validated `GateRequest`. Returns `Err` if `verb` is empty.
64    pub fn try_new(
65        actor: ActorRef,
66        namespace: Namespace,
67        verb: impl Into<String>,
68        args: serde_json::Value,
69    ) -> Result<Self, GateValidationError> {
70        let verb = verb.into();
71        if verb.is_empty() {
72            return Err(GateValidationError::EmptyVerb);
73        }
74        Ok(Self {
75            actor,
76            namespace,
77            verb,
78            args,
79            context: GateContext::default(),
80        })
81    }
82
83    /// Builds a `GateRequest` with default (empty) context. Panics if `verb` is empty.
84    pub fn new(
85        actor: ActorRef,
86        namespace: Namespace,
87        verb: impl Into<String>,
88        args: serde_json::Value,
89    ) -> Self {
90        Self::try_new(actor, namespace, verb, args)
91            .expect("GateRequest::new: verb must not be empty")
92    }
93
94    /// Attaches a `GateContext` (session, timestamp, source) to this request.
95    pub fn with_context(mut self, context: GateContext) -> Self {
96        self.context = context;
97        self
98    }
99}