Skip to main content

harmos_runtime/operations/
middleware.rs

1/* ***
2 *
3 * Harmos
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * ***
8 */
9
10/* === Modules === */
11
12/* === Re-Exports === */
13
14/* === Imports === */
15
16// Standard Library Imports
17use std::future::Future;
18use std::pin::Pin;
19use std::sync::{Arc, Mutex, OnceLock};
20
21// Third Party Imports
22
23// Local Imports
24use super::{CallContext, OperationGeneration, OperationInstanceId, OperationOutcome};
25use crate::operations::OperationDefinition;
26use crate::{Error, Result};
27
28/* === Constants === */
29
30/* === Macros === */
31
32/* === Type Aliases === */
33
34pub type Enter<'a> = Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>>;
35
36/* === Structs === */
37
38pub struct Invocation {
39    operation: OperationDefinition,
40    id: OperationInstanceId,
41    generation: OperationGeneration,
42    context: CallContext,
43    origin: super::caller::Origin,
44}
45
46#[derive(Clone, Copy, Debug)]
47pub struct Observation {
48    pub outcome: OperationOutcome,
49    pub settled: bool,
50}
51
52#[derive(Default)]
53pub(super) struct Configuration {
54    state: Mutex<State>,
55}
56
57#[derive(Default)]
58struct State {
59    frozen: bool,
60    entries: Vec<Arc<dyn Middleware>>,
61    authorizer: Option<Arc<dyn super::authorization::Authorizer>>,
62    authorization_frozen: bool,
63}
64
65pub(super) struct Session {
66    call: Invocation,
67    entries: Vec<Arc<dyn Middleware>>,
68    entered: usize,
69    observed: bool,
70    context: Arc<OnceLock<CallContext>>,
71}
72
73/* === Enums === */
74
75/* === Traits === */
76
77pub trait Middleware: Send + Sync + 'static {
78    fn enter<'a>(&'a self, call: &'a mut Invocation) -> Enter<'a>;
79
80    fn observe(&self, _call: &Invocation, _observation: Observation) {}
81}
82
83/* === Implementations === */
84
85impl Invocation {
86    pub fn is_host_call(&self) -> bool {
87        self.origin.identity == super::caller::Identity::Host
88    }
89
90    #[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
91    pub fn caller_scope(&self) -> Option<&crate::plugin::PluginScope> {
92        self.origin.scope.as_ref()
93    }
94
95    pub fn operation(&self) -> &OperationDefinition {
96        &self.operation
97    }
98
99    pub fn id(&self) -> OperationInstanceId {
100        self.id
101    }
102
103    pub fn generation(&self) -> OperationGeneration {
104        self.generation
105    }
106
107    pub fn context(&self) -> &CallContext {
108        &self.context
109    }
110
111    pub fn context_mut(&mut self) -> crate::operations::ContextMut<'_> {
112        super::ContextMut::new(&mut self.context)
113    }
114}
115
116impl Configuration {
117    pub(super) fn register(&self, middleware: Arc<dyn Middleware>) -> Result<()> {
118        let mut state = self.state.lock().expect("middleware configuration lock is healthy");
119        if state.frozen {
120            return Err(Error::Closing {
121                domain: "operation middleware configuration",
122            });
123        }
124        state.entries.push(middleware);
125        Ok(())
126    }
127
128    pub(super) fn register_authorizer(&self, authorizer: Arc<dyn super::authorization::Authorizer>) -> Result<()> {
129        let mut state = self.state.lock().expect("middleware configuration lock is healthy");
130        if state.authorization_frozen {
131            return Err(Error::Closing {
132                domain: "operation authorization configuration",
133            });
134        }
135        if state.authorizer.is_some() {
136            return Err(Error::InvalidInput {
137                field: "operation authorizer",
138                reason: "authorization already has an owner",
139            });
140        }
141        state.authorizer = Some(authorizer);
142        Ok(())
143    }
144
145    #[cfg(feature = "grpc")]
146    pub(super) fn authorizer(&self) -> Option<Arc<dyn super::authorization::Authorizer>> {
147        let mut state = self.state.lock().expect("middleware configuration lock is healthy");
148        state.authorization_frozen = true;
149        state.authorizer.clone()
150    }
151
152    pub(super) fn capture(&self) -> Vec<Arc<dyn Middleware>> {
153        let mut state = self.state.lock().expect("middleware configuration lock is healthy");
154        state.frozen = true;
155        state.entries.clone()
156    }
157}
158
159impl Session {
160    pub(super) fn new(
161        entries: Vec<Arc<dyn Middleware>>, operation: OperationDefinition, id: OperationInstanceId, generation: OperationGeneration,
162        inherited: CallContext, context: Arc<OnceLock<CallContext>>, origin: super::caller::Origin,
163    ) -> Self {
164        Self {
165            call: Invocation {
166                operation,
167                id,
168                generation,
169                context: inherited,
170                origin,
171            },
172            entries,
173            entered: 0,
174            observed: false,
175            context,
176        }
177    }
178
179    pub(super) async fn enter(&mut self) -> Result<()> {
180        for entry in &self.entries {
181            self.entered += 1;
182            let result = guarded(Box::pin(async { entry.enter(&mut self.call).await })).await;
183            if let Err(error) = result {
184                self.freeze();
185                return Err(error);
186            }
187        }
188        self.freeze();
189        Ok(())
190    }
191
192    fn freeze(&self) {
193        let _ = self.context.set(self.call.context.clone());
194    }
195
196    pub(super) fn observe(&mut self, observation: Observation) {
197        if self.observed {
198            return;
199        }
200        self.observed = true;
201        for entry in self.entries[..self.entered].iter().rev() {
202            if std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
203                entry.observe(&self.call, observation);
204            }))
205            .is_err()
206            {
207                tracing::error!(operation = %self.call.operation.id, "operation middleware observation panicked");
208            }
209        }
210    }
211}
212
213impl Drop for Session {
214    fn drop(&mut self) {
215        self.observe(Observation {
216            outcome: OperationOutcome::Unavailable,
217            settled: false,
218        });
219    }
220}
221
222/* === Public Functions === */
223
224/* === Private Functions === */
225
226async fn guarded(mut future: Enter<'_>) -> Result<()> {
227    std::future::poll_fn(|context| match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| future.as_mut().poll(context))) {
228        Ok(result) => result,
229        Err(_) => std::task::Poll::Ready(Err(Error::InvalidInput {
230            field: "operation middleware",
231            reason: "middleware entry panicked",
232        })),
233    })
234    .await
235}
236
237/* === Tests === */