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}
46
47/// Transport-specific execution details.
48#[derive(Debug, Clone, PartialEq, Eq)]
49pub struct TransportExecutionContext {
50    pub pattern: String,
51    pub kind: ExecutionTransportKind,
52}
53
54/// Context visible to guards, interceptors, pipes, and filters.
55#[derive(Debug, Clone)]
56pub struct ExecutionContext {
57    pub protocol: ExecutionProtocol,
58    pub method: HttpMethod,
59    pub request_path: String,
60    pub route_path: String,
61    pub module_name: Option<String>,
62    pub controller_prefix: Option<String>,
63    pub serialization: SerializationOptions,
64    pub metadata: BTreeMap<String, Value>,
65    pub request: BootRequest,
66    pub websocket: Option<WebSocketExecutionContext>,
67    pub transport: Option<TransportExecutionContext>,
68}
69
70impl ExecutionContext {
71    pub(crate) fn new(
72        request: BootRequest,
73        route_path: String,
74        module_name: Option<String>,
75        controller_prefix: Option<String>,
76        serialization: SerializationOptions,
77        metadata: BTreeMap<String, Value>,
78    ) -> Self {
79        Self {
80            protocol: ExecutionProtocol::Http,
81            method: request.method,
82            request_path: request.path.clone(),
83            route_path,
84            module_name,
85            controller_prefix,
86            serialization,
87            metadata,
88            request,
89            websocket: None,
90            transport: None,
91        }
92    }
93
94    pub(crate) fn websocket(
95        request: BootRequest,
96        gateway_path: String,
97        event: String,
98        module_name: Option<String>,
99    ) -> Self {
100        Self {
101            protocol: ExecutionProtocol::WebSocket,
102            method: request.method,
103            request_path: request.path.clone(),
104            route_path: gateway_path.clone(),
105            module_name,
106            controller_prefix: None,
107            serialization: SerializationOptions::default(),
108            metadata: BTreeMap::new(),
109            request,
110            websocket: Some(WebSocketExecutionContext {
111                gateway_path,
112                event,
113            }),
114            transport: None,
115        }
116    }
117
118    pub(crate) fn transport(
119        pattern: String,
120        kind: ExecutionTransportKind,
121        module_name: Option<String>,
122    ) -> Self {
123        Self {
124            protocol: ExecutionProtocol::Transport,
125            method: HttpMethod::Post,
126            request_path: pattern.clone(),
127            route_path: pattern.clone(),
128            module_name,
129            controller_prefix: None,
130            serialization: SerializationOptions::default(),
131            metadata: BTreeMap::new(),
132            request: BootRequest::new(HttpMethod::Post, "/__transport"),
133            websocket: None,
134            transport: Some(TransportExecutionContext { pattern, kind }),
135        }
136    }
137
138    pub fn protocol(&self) -> ExecutionProtocol {
139        self.protocol
140    }
141
142    pub fn websocket_context(&self) -> Option<&WebSocketExecutionContext> {
143        self.websocket.as_ref()
144    }
145
146    pub fn transport_context(&self) -> Option<&TransportExecutionContext> {
147        self.transport.as_ref()
148    }
149
150    pub fn metadata_value(&self, key: &str) -> Option<&Value> {
151        self.metadata.get(key)
152    }
153
154    pub fn metadata_as<T>(&self, key: &str) -> Result<Option<T>>
155    where
156        T: DeserializeOwned,
157    {
158        let Some(value) = self.metadata.get(key) else {
159            return Ok(None);
160        };
161
162        serde_json::from_value(value.clone())
163            .map(Some)
164            .map_err(|error| {
165                BootError::Internal(format!(
166                    "failed to deserialize execution context metadata `{key}`: {error}"
167                ))
168            })
169    }
170}