use std::{
borrow::Cow,
collections::{BTreeMap, HashMap},
sync::Arc,
};
use crate::vrl::expressions::{ToVrlValue, VrlValue};
use bytes::Bytes;
use http::{Method, Uri};
use ntex::{http::HeaderMap as NtexHeaderMap, router::Path};
use crate::executor::request_context::{RequestContextError, SharedRequestContext};
pub struct OperationDetails<'exec> {
pub name: Option<&'exec str>,
pub query: &'exec str,
pub kind: &'static str,
}
type PathParamsMap<'exec> = HashMap<Cow<'exec, str>, Cow<'exec, str>>;
#[derive(Debug, Clone, Default)]
pub struct PathParams<'exec>(PathParamsMap<'exec>);
impl<'exec> From<&'exec Path<Uri>> for PathParams<'exec> {
fn from(path: &'exec Path<Uri>) -> Self {
Self(
path.iter()
.map(|(key, value)| (key.into(), value.into()))
.collect(),
)
}
}
impl<'a> std::ops::Deref for PathParams<'a> {
type Target = PathParamsMap<'a>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'exec> PathParams<'exec> {
pub fn into_owned(&self) -> PathParams<'static> {
PathParams(
self.0
.clone()
.into_iter()
.map(|(key, value)| (Cow::Owned(key.into_owned()), Cow::Owned(value.into_owned())))
.collect(),
)
}
}
pub struct MutableClientRequestDetails<'exec> {
pub method: &'exec Method,
pub url: &'exec http::Uri,
pub headers: NtexHeaderMap,
pub operation: OperationDetails<'exec>,
pub jwt: Arc<JwtRequestDetails>,
pub path_params: PathParams<'exec>,
}
pub struct ClientRequestDetails<'exec> {
pub method: &'exec Method,
pub url: &'exec http::Uri,
pub headers: Arc<NtexHeaderMap>,
pub operation: OperationDetails<'exec>,
pub jwt: Arc<JwtRequestDetails>,
pub path_params: PathParams<'exec>,
}
pub trait ClientRequestDetailsView {
fn method(&self) -> &Method;
fn url(&self) -> &http::Uri;
fn headers(&self) -> &NtexHeaderMap;
fn operation<'a>(&'a self) -> &'a OperationDetails<'a>;
fn jwt(&self) -> &JwtRequestDetails;
fn path_params<'a>(&'a self) -> &'a PathParams<'a>;
fn to_vrl_value(&self) -> VrlValue {
request_details_to_vrl_value(self)
}
}
impl ClientRequestDetailsView for MutableClientRequestDetails<'_> {
fn method(&self) -> &Method {
self.method
}
fn url(&self) -> &http::Uri {
self.url
}
fn headers(&self) -> &NtexHeaderMap {
&self.headers
}
fn operation<'a>(&'a self) -> &'a OperationDetails<'a> {
&self.operation
}
fn jwt(&self) -> &JwtRequestDetails {
&self.jwt
}
fn path_params<'a>(&'a self) -> &'a PathParams<'a> {
&self.path_params
}
}
impl ClientRequestDetailsView for ClientRequestDetails<'_> {
fn method(&self) -> &Method {
self.method
}
fn url(&self) -> &http::Uri {
self.url
}
fn headers(&self) -> &NtexHeaderMap {
&self.headers
}
fn operation<'a>(&'a self) -> &'a OperationDetails<'a> {
&self.operation
}
fn jwt(&self) -> &JwtRequestDetails {
&self.jwt
}
fn path_params<'a>(&'a self) -> &'a PathParams<'a> {
&self.path_params
}
}
impl<'exec> MutableClientRequestDetails<'exec> {
pub fn freeze(self) -> ClientRequestDetails<'exec> {
ClientRequestDetails {
method: self.method,
url: self.url,
headers: self.headers.into(),
operation: self.operation,
jwt: self.jwt,
path_params: self.path_params,
}
}
}
impl From<&MutableClientRequestDetails<'_>> for VrlValue {
fn from(details: &MutableClientRequestDetails) -> Self {
request_details_to_vrl_value(details)
}
}
pub enum JwtRequestDetails {
Authenticated {
token: String,
prefix: Option<String>,
claims: sonic_rs::Value,
scopes: Option<Vec<String>>,
},
Unauthenticated,
}
impl JwtRequestDetails {
pub fn update_request_context(
&self,
request_context: &SharedRequestContext,
) -> Result<(), RequestContextError> {
request_context.update(|ctx| match self {
JwtRequestDetails::Authenticated { scopes, .. } => {
ctx.authentication.jwt_status = Some(true);
ctx.authentication.jwt_scopes = scopes
.as_ref()
.map(|scopes| scopes.iter().cloned().collect());
}
JwtRequestDetails::Unauthenticated => {
ctx.authentication.jwt_scopes = None;
ctx.authentication.jwt_status = Some(false);
}
})
}
}
impl From<&ClientRequestDetails<'_>> for VrlValue {
fn from(details: &ClientRequestDetails) -> Self {
request_details_to_vrl_value(details)
}
}
pub fn ntex_header_map_to_vrl_value(headers: &NtexHeaderMap) -> VrlValue {
let mut obj = BTreeMap::new();
for (header_name, header_value) in headers.iter() {
if let Ok(value) = header_value.to_str() {
obj.insert(
header_name.as_str().into(),
VrlValue::Bytes(Bytes::from(value.to_owned())),
);
}
}
VrlValue::Object(obj)
}
fn request_details_to_vrl_value(details: &(impl ClientRequestDetailsView + ?Sized)) -> VrlValue {
let headers_value = ntex_header_map_to_vrl_value(details.headers());
let url_value = details.url().to_vrl_value();
let path_params_value = VrlValue::Object(
details
.path_params()
.iter()
.map(|(k, v)| (k.as_ref().into(), v.as_ref().into()))
.collect(),
);
let operation_value = VrlValue::Object(BTreeMap::from([
("name".into(), details.operation().name.into()),
("type".into(), details.operation().kind.into()),
("query".into(), details.operation().query.into()),
]));
let jwt_value = match details.jwt() {
JwtRequestDetails::Authenticated {
token,
prefix,
claims,
scopes,
} => VrlValue::Object(BTreeMap::from([
("authenticated".into(), VrlValue::Boolean(true)),
("token".into(), token.as_str().into()),
(
"prefix".into(),
prefix.as_deref().unwrap_or_default().into(),
),
("claims".into(), claims.to_vrl_value()),
(
"scopes".into(),
match scopes {
Some(scopes) => {
VrlValue::Array(scopes.iter().map(|v| v.as_str().into()).collect())
}
None => VrlValue::Array(vec![]),
},
),
])),
JwtRequestDetails::Unauthenticated => VrlValue::Object(BTreeMap::from([
("authenticated".into(), VrlValue::Boolean(false)),
("token".into(), VrlValue::Null),
("prefix".into(), VrlValue::Null),
("claims".into(), VrlValue::Object(BTreeMap::new())),
("scopes".into(), VrlValue::Array(vec![])),
])),
};
VrlValue::Object(BTreeMap::from([
("method".into(), details.method().as_str().into()),
("headers".into(), headers_value),
("url".into(), url_value),
("path_params".into(), path_params_value),
("operation".into(), operation_value),
("jwt".into(), jwt_value),
]))
}