pdk_classy/extract/
context.rs

1// Copyright (c) 2025, Salesforce, Inc.,
2// All rights reserved.
3// For full license text, see the LICENSE.txt file
4
5//! The different contexts where data can be extracted from.
6
7use std::any::TypeId;
8use std::collections::HashSet;
9use std::{cell::RefCell, convert::Infallible, rc::Rc};
10
11use crate::host::clock::Clock;
12use crate::host::grpc::GrpcHost;
13use crate::host::shared_data::SharedData;
14use crate::{
15    context,
16    event::{After, Exchange, Start},
17    host::Host,
18    reactor::{http::HttpReactor, root::RootReactor},
19    types::{HttpCid, RootCid},
20    BoxFuture,
21};
22
23use super::{extractability, AlreadyExtracted, Exclusive, FromContext, FromContextOnce};
24
25/// Context representing the configuration of the plugin.
26/// The configuration function is the one annotated with the `#[entrypoint]` annotation.
27pub struct ConfigureContext {
28    pub(crate) host: Rc<dyn Host>,
29    pub(crate) clock: Rc<dyn Clock>,
30    pub(crate) grpc_host: Rc<dyn GrpcHost>,
31    pub(crate) shared_data: Rc<dyn SharedData>,
32    #[cfg(feature = "experimental_metrics")]
33    pub(crate) metrics: Rc<dyn crate::host::metrics::MetricsHost>,
34    pub(crate) root_reactor: Rc<RootReactor>,
35    pub(crate) unique_extractions: RefCell<HashSet<TypeId>>,
36}
37
38context!(ConfigureContext);
39
40impl ConfigureContext {
41    pub(crate) fn extract_unique<T: 'static>(
42        &self,
43        op: impl FnOnce() -> T,
44    ) -> Result<T, AlreadyExtracted<T>> {
45        self.unique_extractions
46            .borrow_mut()
47            .insert(TypeId::of::<T>())
48            .then(op)
49            .ok_or_else(AlreadyExtracted::default)
50    }
51}
52
53impl FromContext<ConfigureContext, extractability::Transitive> for Rc<dyn Host> {
54    type Error = Infallible;
55
56    fn from_context(context: &ConfigureContext) -> Result<Self, Self::Error> {
57        Ok(context.host.clone())
58    }
59}
60
61impl FromContext<ConfigureContext, extractability::Transitive> for Rc<dyn Clock> {
62    type Error = Infallible;
63
64    fn from_context(context: &ConfigureContext) -> Result<Self, Self::Error> {
65        Ok(context.clock.clone())
66    }
67}
68
69impl FromContext<ConfigureContext, extractability::Transitive> for Rc<dyn SharedData> {
70    type Error = Infallible;
71
72    fn from_context(context: &ConfigureContext) -> Result<Self, Self::Error> {
73        Ok(context.shared_data.clone())
74    }
75}
76
77#[cfg(feature = "experimental_metrics")]
78impl FromContext<ConfigureContext, extractability::Transitive>
79    for Rc<dyn crate::host::metrics::MetricsHost>
80{
81    type Error = Infallible;
82
83    fn from_context(context: &ConfigureContext) -> Result<Self, Self::Error> {
84        Ok(context.metrics.clone())
85    }
86}
87
88impl FromContext<ConfigureContext, extractability::Transitive> for Rc<RootReactor> {
89    type Error = Infallible;
90
91    fn from_context(context: &ConfigureContext) -> Result<Self, Self::Error> {
92        Ok(context.root_reactor.clone())
93    }
94}
95
96impl FromContext<ConfigureContext, extractability::Transitive> for RootCid {
97    type Error = Infallible;
98
99    fn from_context(context: &ConfigureContext) -> Result<Self, Self::Error> {
100        Ok(context.root_reactor.context_id())
101    }
102}
103
104/// Context representing the request filter function of the plugin.
105/// The filter function is the one that is provided to the [`Launcher`](crate::bootstrap::Launcher).
106pub struct FilterContext {
107    parent: Rc<ConfigureContext>,
108    http_reactor: Rc<HttpReactor>,
109}
110
111impl FilterContext {
112    pub(crate) fn new(parent: Rc<ConfigureContext>, http_reactor: Rc<HttpReactor>) -> Self {
113        Self {
114            parent,
115            http_reactor,
116        }
117    }
118
119    fn parent(&self) -> &ConfigureContext {
120        &self.parent
121    }
122}
123
124context!(FilterContext => ConfigureContext {FilterContext::parent});
125
126impl<E> FromContextOnce<FilterContext> for Exchange<E>
127where
128    E: After<Start> + 'static,
129{
130    type Error = AlreadyExtracted<Exchange<E>>;
131
132    type Future<'c> = BoxFuture<'c, Result<Self, Self::Error>>;
133
134    fn from_context_once(context: Exclusive<FilterContext>) -> Self::Future<'_> {
135        Box::pin(async move {
136            let exchange = Exchange::new(
137                context.http_reactor.clone(),
138                context.parent.host.clone(),
139                Some(Start {
140                    _context_id: context.http_reactor.context_id(),
141                }),
142            );
143            Ok(exchange.wait_for_event::<E>().await)
144        })
145    }
146}
147
148impl FromContext<FilterContext> for Rc<HttpReactor> {
149    type Error = Infallible;
150
151    fn from_context(context: &FilterContext) -> Result<Self, Self::Error> {
152        Ok(context.http_reactor.clone())
153    }
154}
155
156impl FromContext<FilterContext> for HttpCid {
157    type Error = Infallible;
158
159    fn from_context(context: &FilterContext) -> Result<Self, Self::Error> {
160        Ok(context.http_reactor.context_id())
161    }
162}