Skip to main content

a3s_boot/pipeline/
context.rs

1use crate::{BootError, BootRequest, HttpMethod, Result, SerializationOptions};
2use serde::de::DeserializeOwned;
3use serde_json::Value;
4use std::collections::BTreeMap;
5
6/// Protocol handled by an [`ExecutionContext`].
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum ExecutionProtocol {
9    Http,
10    WebSocket,
11    Transport,
12}
13
14impl ExecutionProtocol {
15    pub fn as_str(self) -> &'static str {
16        match self {
17            Self::Http => "http",
18            Self::WebSocket => "websocket",
19            Self::Transport => "transport",
20        }
21    }
22}
23
24/// Transport handler style visible through protocol-neutral execution context.
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum ExecutionTransportKind {
27    RequestResponse,
28    Event,
29}
30
31impl ExecutionTransportKind {
32    pub fn as_str(self) -> &'static str {
33        match self {
34            Self::RequestResponse => "request-response",
35            Self::Event => "event",
36        }
37    }
38}
39
40/// WebSocket-specific execution details.
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct WebSocketExecutionContext {
43    pub gateway_path: String,
44    pub event: String,
45    pub namespace: Option<String>,
46}
47
48/// Transport-specific execution details.
49#[derive(Debug, Clone, PartialEq, Eq)]
50pub struct TransportExecutionContext {
51    pub pattern: String,
52    pub kind: ExecutionTransportKind,
53}
54
55/// Context visible to guards, interceptors, pipes, and filters.
56#[derive(Debug, Clone)]
57pub struct ExecutionContext {
58    pub protocol: ExecutionProtocol,
59    pub method: HttpMethod,
60    pub request_path: String,
61    pub route_path: String,
62    pub module_name: Option<String>,
63    pub controller_prefix: Option<String>,
64    pub serialization: SerializationOptions,
65    pub metadata: BTreeMap<String, Value>,
66    pub request: BootRequest,
67    pub websocket: Option<WebSocketExecutionContext>,
68    pub transport: Option<TransportExecutionContext>,
69}
70
71impl ExecutionContext {
72    pub(crate) fn new(
73        request: BootRequest,
74        route_path: String,
75        module_name: Option<String>,
76        controller_prefix: Option<String>,
77        serialization: SerializationOptions,
78        metadata: BTreeMap<String, Value>,
79    ) -> Self {
80        Self {
81            protocol: ExecutionProtocol::Http,
82            method: request.method,
83            request_path: request.path.clone(),
84            route_path,
85            module_name,
86            controller_prefix,
87            serialization,
88            metadata,
89            request,
90            websocket: None,
91            transport: None,
92        }
93    }
94
95    pub(crate) fn websocket(
96        request: BootRequest,
97        gateway_path: String,
98        event: String,
99        namespace: Option<String>,
100        module_name: Option<String>,
101        metadata: BTreeMap<String, Value>,
102    ) -> Self {
103        Self {
104            protocol: ExecutionProtocol::WebSocket,
105            method: request.method,
106            request_path: request.path.clone(),
107            route_path: gateway_path.clone(),
108            module_name,
109            controller_prefix: None,
110            serialization: SerializationOptions::default(),
111            metadata,
112            request,
113            websocket: Some(WebSocketExecutionContext {
114                gateway_path,
115                event,
116                namespace,
117            }),
118            transport: None,
119        }
120    }
121
122    pub(crate) fn transport(
123        pattern: String,
124        kind: ExecutionTransportKind,
125        module_name: Option<String>,
126        metadata: BTreeMap<String, Value>,
127    ) -> Self {
128        Self {
129            protocol: ExecutionProtocol::Transport,
130            method: HttpMethod::Post,
131            request_path: pattern.clone(),
132            route_path: pattern.clone(),
133            module_name,
134            controller_prefix: None,
135            serialization: SerializationOptions::default(),
136            metadata,
137            request: BootRequest::new(HttpMethod::Post, "/__transport"),
138            websocket: None,
139            transport: Some(TransportExecutionContext { pattern, kind }),
140        }
141    }
142
143    pub fn protocol(&self) -> ExecutionProtocol {
144        self.protocol
145    }
146
147    pub fn websocket_context(&self) -> Option<&WebSocketExecutionContext> {
148        self.websocket.as_ref()
149    }
150
151    pub fn transport_context(&self) -> Option<&TransportExecutionContext> {
152        self.transport.as_ref()
153    }
154
155    pub fn metadata_value(&self, key: &str) -> Option<&Value> {
156        self.metadata.get(key)
157    }
158
159    pub fn metadata_as<T>(&self, key: &str) -> Result<Option<T>>
160    where
161        T: DeserializeOwned,
162    {
163        let Some(value) = self.metadata.get(key) else {
164            return Ok(None);
165        };
166
167        serde_json::from_value(value.clone())
168            .map(Some)
169            .map_err(|error| {
170                BootError::Internal(format!(
171                    "failed to deserialize execution context metadata `{key}`: {error}"
172                ))
173            })
174    }
175}