Skip to main content

pdk_script/bindings/
attributes.rs

1// Copyright 2023 Salesforce, Inc. All rights reserved.
2use crate::constants::{
3    DESTINATION_ADDRESS, METHOD_HEADER, PATH_HEADER, REQUEST_PROTOCOL, REQUEST_SCHEME,
4    SCHEME_HEADER, SOURCE_ADDRESS, STATUS_CODE_HEADER,
5};
6use classy::event::HeadersAccessor;
7use classy::hl::{HeadersHandler, HttpClientResponse, PropertyAccessor};
8use pel::runtime::value::Value as PelValue;
9use std::collections::HashMap;
10use url::Url;
11
12/// Binding for the top-level `attributes` variable.
13pub trait AttributesBinding {
14    /// Returns the entire headers map.
15    fn extract_headers(&self) -> HashMap<String, String>;
16
17    /// Returns a single header.
18    fn extract_header(&self, key: &str) -> Option<String>;
19
20    /// Returns a map with the query parameters.
21    fn extract_query_params(&self) -> HashMap<String, String> {
22        self.extract_header(PATH_HEADER)
23            .and_then(fake_url)
24            .map(|url| {
25                url.query_pairs()
26                    .map(|(k, v)| (k.to_string(), v.to_string()))
27                    .collect()
28            })
29            .unwrap_or_default()
30    }
31
32    /// Returns the `attributes.method` value.
33    fn method(&self) -> Option<String> {
34        self.extract_header(METHOD_HEADER)
35    }
36
37    /// Returns the `attributes.path` value.
38    fn path(&self) -> Option<String> {
39        let mut url = self.extract_header(PATH_HEADER).and_then(fake_url)?;
40        url.set_query(None);
41        Some(url.path().to_string())
42    }
43
44    /// Returns the `uri` value.
45    fn uri(&self) -> Option<String> {
46        self.extract_header(PATH_HEADER)
47    }
48
49    /// Returns the `attributes.remoteAddress` value.
50    fn remote_address(&self) -> Option<String> {
51        None
52    }
53
54    /// Returns the `attributes.localAddress` value.
55    fn local_address(&self) -> Option<String> {
56        None
57    }
58
59    /// Returns the `attributes.queryString` value.
60    fn query_string(&self) -> Option<String> {
61        let path = self.extract_header(PATH_HEADER)?;
62        fake_url(path)?.query().map(str::to_string)
63    }
64
65    /// Returns the `attributes.scheme` value.
66    fn scheme(&self) -> Option<String> {
67        self.extract_header(SCHEME_HEADER)
68    }
69
70    /// Returns the `attributes.version` value.
71    fn version(&self) -> Option<String> {
72        None
73    }
74
75    /// Returns the `attributes.statusCode` value.
76    fn status_code(&self) -> Option<u32> {
77        self.extract_header(STATUS_CODE_HEADER)
78            .and_then(|value| value.parse::<u32>().ok())
79    }
80}
81
82impl AttributesBinding for HttpClientResponse {
83    fn extract_headers(&self) -> HashMap<String, String> {
84        self.headers().clone()
85    }
86
87    fn extract_header(&self, key: &str) -> Option<String> {
88        self.header(key).cloned()
89    }
90}
91
92pub struct HandlerAttributesBinding<'a> {
93    handler: &'a dyn HeadersHandler,
94    properties: Option<&'a dyn PropertyAccessor>,
95}
96
97impl<'a> HandlerAttributesBinding<'a> {
98    pub fn new(handler: &'a dyn HeadersHandler, properties: &'a dyn PropertyAccessor) -> Self {
99        Self {
100            handler,
101            properties: Some(properties),
102        }
103    }
104
105    /// Will create a binding that won't be able to resolve remote_address, local_address, scheme or version
106    pub fn partial(handler: &'a dyn HeadersHandler) -> Self {
107        Self {
108            handler,
109            properties: None,
110        }
111    }
112}
113
114impl AttributesBinding for HandlerAttributesBinding<'_> {
115    fn extract_headers(&self) -> HashMap<String, String> {
116        self.handler.headers().into_iter().collect()
117    }
118
119    fn extract_header(&self, key: &str) -> Option<String> {
120        self.handler.header(key)
121    }
122
123    fn remote_address(&self) -> Option<String> {
124        self.properties.and_then(remote_address)
125    }
126
127    fn local_address(&self) -> Option<String> {
128        self.properties.and_then(local_address)
129    }
130
131    fn scheme(&self) -> Option<String> {
132        self.properties.and_then(scheme)
133    }
134
135    fn version(&self) -> Option<String> {
136        self.properties.and_then(version)
137    }
138}
139
140pub struct AccessorAttributesBinding<'a> {
141    accessor: &'a dyn HeadersAccessor,
142    properties: Option<&'a dyn PropertyAccessor>,
143}
144
145impl<'a> AccessorAttributesBinding<'a> {
146    pub fn new(handler: &'a dyn HeadersAccessor, properties: &'a dyn PropertyAccessor) -> Self {
147        Self {
148            accessor: handler,
149            properties: Some(properties),
150        }
151    }
152
153    /// Will create a binding that won't be able to resolve remote_address, local_address, scheme or version
154    pub fn partial(accessor: &'a dyn HeadersAccessor) -> Self {
155        Self {
156            accessor,
157            properties: None,
158        }
159    }
160}
161
162impl AttributesBinding for AccessorAttributesBinding<'_> {
163    fn extract_headers(&self) -> HashMap<String, String> {
164        self.accessor.headers().into_iter().collect()
165    }
166
167    fn extract_header(&self, key: &str) -> Option<String> {
168        self.accessor.header(key)
169    }
170
171    fn remote_address(&self) -> Option<String> {
172        self.properties.and_then(remote_address)
173    }
174
175    fn local_address(&self) -> Option<String> {
176        self.properties.and_then(local_address)
177    }
178
179    fn scheme(&self) -> Option<String> {
180        self.properties.and_then(scheme)
181    }
182
183    fn version(&self) -> Option<String> {
184        self.properties.and_then(version)
185    }
186}
187
188pub(crate) struct AttributesBindingAdapter<'a> {
189    delegate: &'a dyn AttributesBinding,
190}
191
192impl<'a> AttributesBindingAdapter<'a> {
193    pub fn new(delegate: &'a dyn AttributesBinding) -> Self {
194        Self { delegate }
195    }
196
197    pub fn extract_headers(&self) -> HashMap<String, PelValue> {
198        convert_maps(self.delegate.extract_headers())
199    }
200
201    pub fn extract_header(&self, key: &str) -> Option<PelValue> {
202        convert_string(self.delegate.extract_header(key))
203    }
204
205    pub fn extract_query_params(&self) -> HashMap<String, PelValue> {
206        convert_maps(self.delegate.extract_query_params())
207    }
208
209    pub fn method(&self) -> Option<PelValue> {
210        convert_string(self.delegate.method())
211    }
212
213    pub fn path(&self) -> Option<PelValue> {
214        convert_string(self.delegate.path())
215    }
216
217    pub fn uri(&self) -> Option<PelValue> {
218        convert_string(self.delegate.uri())
219    }
220
221    pub fn remote_address(&self) -> Option<PelValue> {
222        convert_string(self.delegate.remote_address())
223    }
224
225    pub fn local_address(&self) -> Option<PelValue> {
226        convert_string(self.delegate.local_address())
227    }
228
229    pub fn query_string(&self) -> Option<PelValue> {
230        convert_string(self.delegate.query_string())
231    }
232
233    pub fn scheme(&self) -> Option<PelValue> {
234        convert_string(self.delegate.scheme())
235    }
236
237    pub fn version(&self) -> Option<PelValue> {
238        convert_string(self.delegate.version())
239    }
240
241    pub fn status_code(&self) -> Option<PelValue> {
242        self.delegate
243            .status_code()
244            .map(|s| PelValue::number(s as f64))
245    }
246}
247
248fn fake_url(uri: String) -> Option<Url> {
249    Url::parse("http://fake_base").ok()?.join(uri.as_str()).ok()
250}
251fn convert_maps(map: HashMap<String, String>) -> HashMap<String, PelValue> {
252    map.into_iter()
253        .map(|(key, val)| (key, PelValue::string(val)))
254        .collect()
255}
256
257fn convert_string(val: Option<String>) -> Option<PelValue> {
258    val.map(PelValue::string)
259}
260
261fn remote_address(properties: &dyn PropertyAccessor) -> Option<String> {
262    properties
263        .read_property(SOURCE_ADDRESS)
264        .map(|bytes| String::from_utf8_lossy(bytes.as_slice()).to_string())
265}
266
267fn local_address(properties: &dyn PropertyAccessor) -> Option<String> {
268    properties
269        .read_property(DESTINATION_ADDRESS)
270        .map(|bytes| String::from_utf8_lossy(bytes.as_slice()).to_string())
271}
272
273fn scheme(properties: &dyn PropertyAccessor) -> Option<String> {
274    properties
275        .read_property(REQUEST_SCHEME)
276        .map(|bytes| String::from_utf8_lossy(bytes.as_slice()).to_string())
277}
278
279fn version(properties: &dyn PropertyAccessor) -> Option<String> {
280    properties
281        .read_property(REQUEST_PROTOCOL)
282        .map(|bytes| String::from_utf8_lossy(bytes.as_slice()).to_string())
283}