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        request: BootRequest,
124        pattern: String,
125        kind: ExecutionTransportKind,
126        module_name: Option<String>,
127        metadata: BTreeMap<String, Value>,
128    ) -> Self {
129        Self {
130            protocol: ExecutionProtocol::Transport,
131            method: HttpMethod::Post,
132            request_path: pattern.clone(),
133            route_path: pattern.clone(),
134            module_name,
135            controller_prefix: None,
136            serialization: SerializationOptions::default(),
137            metadata,
138            request,
139            websocket: None,
140            transport: Some(TransportExecutionContext { pattern, kind }),
141        }
142    }
143
144    pub fn protocol(&self) -> ExecutionProtocol {
145        self.protocol
146    }
147
148    pub fn websocket_context(&self) -> Option<&WebSocketExecutionContext> {
149        self.websocket.as_ref()
150    }
151
152    pub fn transport_context(&self) -> Option<&TransportExecutionContext> {
153        self.transport.as_ref()
154    }
155
156    pub fn metadata_value(&self, key: &str) -> Option<&Value> {
157        self.metadata.get(key)
158    }
159
160    pub fn metadata_as<T>(&self, key: &str) -> Result<Option<T>>
161    where
162        T: DeserializeOwned,
163    {
164        let Some(value) = self.metadata.get(key) else {
165            return Ok(None);
166        };
167
168        serde_json::from_value(value.clone())
169            .map(Some)
170            .map_err(|error| {
171                BootError::Internal(format!(
172                    "failed to deserialize execution context metadata `{key}`: {error}"
173                ))
174            })
175    }
176}