use std::fmt::{Debug, Display, Error, Formatter};
use crate::instrumentation::{sensitivity::Sensitive, MakeFmt};
#[derive(Debug, Default, PartialEq, Eq)]
pub struct QueryMarker {
pub key: bool,
pub value: bool,
}
#[allow(missing_debug_implementations)]
pub struct Query<'a, F> {
query: &'a str,
marker: F,
}
impl<'a, F> Query<'a, F> {
pub fn new(query: &'a str, marker: F) -> Self {
Self { query, marker }
}
}
#[inline]
fn write_pair<'a, F>(section: &'a str, marker: F, f: &mut Formatter<'_>) -> Result<(), Error>
where
F: Fn(&'a str) -> QueryMarker,
{
if let Some((key, value)) = section.split_once('=') {
match (marker)(key) {
QueryMarker { key: true, value: true } => write!(f, "{}={}", Sensitive(key), Sensitive(value)),
QueryMarker {
key: true,
value: false,
} => write!(f, "{}={value}", Sensitive(key)),
QueryMarker {
key: false,
value: true,
} => write!(f, "{key}={}", Sensitive(value)),
QueryMarker {
key: false,
value: false,
} => write!(f, "{key}={value}"),
}
} else {
write!(f, "{section}")
}
}
impl<'a, F> Display for Query<'a, F>
where
F: Fn(&'a str) -> QueryMarker,
{
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
let mut it = self.query.split('&');
if let Some(section) = it.next() {
write_pair(section, &self.marker, f)?;
}
for section in it {
write!(f, "&")?;
write_pair(section, &self.marker, f)?;
}
Ok(())
}
}
#[derive(Clone)]
pub struct MakeQuery<F>(pub(crate) F);
impl<F> Debug for MakeQuery<F> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
f.debug_tuple("MakeQuery").field(&"...").finish()
}
}
impl<'a, F> MakeFmt<&'a str> for MakeQuery<F>
where
F: Clone,
{
type Target = Query<'a, F>;
fn make(&self, path: &'a str) -> Self::Target {
Query::new(path, self.0.clone())
}
}
#[cfg(test)]
mod tests {
use http::Uri;
use crate::instrumentation::sensitivity::uri::tests::{
ALL_KEYS_QUERY_STRING_EXAMPLES, ALL_PAIRS_QUERY_STRING_EXAMPLES, ALL_VALUES_QUERY_STRING_EXAMPLES, EXAMPLES,
QUERY_STRING_EXAMPLES, X_QUERY_STRING_EXAMPLES,
};
use super::*;
#[test]
fn mark_none() {
let originals = EXAMPLES.into_iter().chain(QUERY_STRING_EXAMPLES).map(Uri::from_static);
for original in originals {
if let Some(query) = original.query() {
let output = Query::new(query, |_| QueryMarker::default()).to_string();
assert_eq!(output, query, "original = {original}");
}
}
}
#[test]
fn mark_all_keys() {
let originals = QUERY_STRING_EXAMPLES.into_iter().map(Uri::from_static);
let expecteds = ALL_KEYS_QUERY_STRING_EXAMPLES.into_iter().map(Uri::from_static);
for (original, expected) in originals.zip(expecteds) {
let output = Query::new(original.query().unwrap(), |_| QueryMarker {
key: true,
value: false,
})
.to_string();
assert_eq!(output, expected.query().unwrap(), "original = {original}");
}
}
#[test]
fn mark_all_values() {
let originals = QUERY_STRING_EXAMPLES.into_iter().map(Uri::from_static);
let expecteds = ALL_VALUES_QUERY_STRING_EXAMPLES.into_iter().map(Uri::from_static);
for (original, expected) in originals.zip(expecteds) {
let output = Query::new(original.query().unwrap(), |_| QueryMarker {
key: false,
value: true,
})
.to_string();
assert_eq!(output, expected.query().unwrap(), "original = {original}");
}
}
#[test]
fn mark_all_pairs() {
let originals = QUERY_STRING_EXAMPLES.into_iter().map(Uri::from_static);
let expecteds = ALL_PAIRS_QUERY_STRING_EXAMPLES.into_iter().map(Uri::from_static);
for (original, expected) in originals.zip(expecteds) {
let output = Query::new(original.query().unwrap(), |_| QueryMarker { key: true, value: true }).to_string();
assert_eq!(output, expected.query().unwrap(), "original = {original}");
}
}
#[test]
fn mark_x() {
let originals = QUERY_STRING_EXAMPLES.into_iter().map(Uri::from_static);
let expecteds = X_QUERY_STRING_EXAMPLES.into_iter().map(Uri::from_static);
for (original, expected) in originals.zip(expecteds) {
let output = Query::new(original.query().unwrap(), |key| QueryMarker {
key: false,
value: key == "x",
})
.to_string();
assert_eq!(output, expected.query().unwrap(), "original = {original}");
}
}
}