hive_router_plan_executor/headers/
expression.rs

1use std::collections::BTreeMap;
2
3use bytes::Bytes;
4use http::{HeaderMap, HeaderValue};
5use tracing::warn;
6use vrl::core::Value;
7
8use crate::{
9    execution::plan::ClientRequestDetails,
10    headers::{request::RequestExpressionContext, response::ResponseExpressionContext},
11};
12
13fn warn_unsupported_conversion_option<T>(type_name: &str) -> Option<T> {
14    warn!(
15        "Cannot convert VRL {} value to a header value. Please convert it to a string first.",
16        type_name
17    );
18
19    None
20}
21
22pub fn vrl_value_to_header_value(value: Value) -> Option<HeaderValue> {
23    match value {
24        Value::Bytes(bytes) => HeaderValue::from_bytes(&bytes).ok(),
25        Value::Float(f) => HeaderValue::from_str(&f.to_string()).ok(),
26        Value::Boolean(b) => HeaderValue::from_str(if b { "true" } else { "false" }).ok(),
27        Value::Integer(i) => HeaderValue::from_str(&i.to_string()).ok(),
28        Value::Array(_) => warn_unsupported_conversion_option("Array"),
29        Value::Regex(_) => warn_unsupported_conversion_option("Regex"),
30        Value::Timestamp(_) => warn_unsupported_conversion_option("Timestamp"),
31        Value::Object(_) => warn_unsupported_conversion_option("Object"),
32        Value::Null => {
33            warn!("Cannot convert VRL Null value to a header value.");
34            None
35        }
36    }
37}
38
39fn header_map_to_vrl_value(headers: &HeaderMap) -> Value {
40    let mut obj = BTreeMap::new();
41    for (header_name, header_value) in headers.iter() {
42        if let Ok(value) = header_value.to_str() {
43            obj.insert(
44                header_name.as_str().into(),
45                Value::Bytes(Bytes::from(value.to_owned())),
46            );
47        }
48    }
49    Value::Object(obj)
50}
51
52fn client_header_map_to_vrl_value(headers: &ntex_http::HeaderMap) -> Value {
53    let mut obj = BTreeMap::new();
54    for (header_name, header_value) in headers.iter() {
55        if let Ok(value) = header_value.to_str() {
56            obj.insert(
57                header_name.as_str().into(),
58                Value::Bytes(Bytes::from(value.to_owned())),
59            );
60        }
61    }
62    Value::Object(obj)
63}
64
65impl From<&RequestExpressionContext<'_>> for Value {
66    /// NOTE: If performance becomes an issue, consider pre-computing parts of this context that do not change
67    fn from(ctx: &RequestExpressionContext) -> Self {
68        // .subgraph
69        let subgraph_value = {
70            let value = Self::Bytes(Bytes::from(ctx.subgraph_name.to_owned()));
71            Self::Object(BTreeMap::from([("name".into(), value)]))
72        };
73
74        // .request
75        let request_value: Self = ctx.client_request.into();
76
77        Self::Object(BTreeMap::from([
78            ("subgraph".into(), subgraph_value),
79            ("request".into(), request_value),
80        ]))
81    }
82}
83
84impl From<&ResponseExpressionContext<'_>> for Value {
85    /// NOTE: If performance becomes an issue, consider pre-computing parts of this context that do not change
86    fn from(ctx: &ResponseExpressionContext) -> Self {
87        // .subgraph
88        let subgraph_value = Self::Object(BTreeMap::from([(
89            "name".into(),
90            Self::Bytes(Bytes::from(ctx.subgraph_name.to_owned())),
91        )]));
92        // .response
93        let response_value = header_map_to_vrl_value(ctx.subgraph_headers);
94        // .request
95        let request_value: Self = ctx.client_request.into();
96
97        Self::Object(BTreeMap::from([
98            ("subgraph".into(), subgraph_value),
99            ("response".into(), response_value),
100            ("request".into(), request_value),
101        ]))
102    }
103}
104
105impl From<&ClientRequestDetails<'_>> for Value {
106    fn from(details: &ClientRequestDetails) -> Self {
107        // .request.headers
108        let headers_value = client_header_map_to_vrl_value(details.headers);
109
110        // .request.url
111        let url_value = Self::Object(BTreeMap::from([
112            (
113                "host".into(),
114                details.url.host().unwrap_or("unknown").into(),
115            ),
116            ("path".into(), details.url.path().into()),
117            (
118                "port".into(),
119                details
120                    .url
121                    .port_u16()
122                    .unwrap_or_else(|| {
123                        if details.url.scheme() == Some(&http::uri::Scheme::HTTPS) {
124                            443
125                        } else {
126                            80
127                        }
128                    })
129                    .into(),
130            ),
131        ]));
132
133        // .request.operation
134        let operation_value = Self::Object(BTreeMap::from([
135            ("name".into(), details.operation.name.clone().into()),
136            ("type".into(), details.operation.kind.into()),
137            ("query".into(), details.operation.query.clone().into()),
138        ]));
139
140        Self::Object(BTreeMap::from([
141            ("method".into(), details.method.as_str().into()),
142            ("headers".into(), headers_value),
143            ("url".into(), url_value),
144            ("operation".into(), operation_value),
145        ]))
146    }
147}