#[cfg(feature = "properties")]
use std::cell::RefCell;
#[cfg_attr(not(feature = "properties"), allow(dead_code))]
#[derive(Clone, Debug)]
pub(crate) struct ScalarRedactionCtx {
pub(crate) raw: String,
pub(crate) effective: String,
}
#[cfg(feature = "properties")]
#[derive(Default)]
struct InterpRedactionScope {
pairs: Vec<ScalarRedactionCtx>,
}
#[cfg(feature = "properties")]
thread_local! {
static INTERP_REDACTION: RefCell<Vec<InterpRedactionScope>> = const { RefCell::new(Vec::new()) };
}
#[cfg(feature = "properties")]
pub(crate) struct ScalarRedactionGuard {
ctx: Option<ScalarRedactionCtx>,
}
#[cfg(not(feature = "properties"))]
pub(crate) struct ScalarRedactionGuard;
#[cfg(feature = "properties")]
pub(crate) struct InterpRedactionScopeGuard;
#[cfg(feature = "properties")]
impl InterpRedactionScopeGuard {
pub(crate) fn new() -> Self {
INTERP_REDACTION.with(|cell| {
cell.borrow_mut().push(InterpRedactionScope::default());
});
Self
}
}
#[cfg(feature = "properties")]
impl ScalarRedactionGuard {
pub(crate) fn new(ctx: ScalarRedactionCtx) -> Self {
INTERP_REDACTION.with(|cell| {
let mut stack = cell.borrow_mut();
for scope in stack.iter_mut() {
if !scope
.pairs
.iter()
.any(|pair| pair.raw == ctx.raw && pair.effective == ctx.effective)
{
scope.pairs.push(ctx.clone());
}
}
});
Self { ctx: Some(ctx) }
}
}
#[cfg(not(feature = "properties"))]
impl ScalarRedactionGuard {
pub(crate) fn new(_: ScalarRedactionCtx) -> Self {
Self
}
}
#[cfg(feature = "properties")]
impl Drop for ScalarRedactionGuard {
fn drop(&mut self) {
let _ = self.ctx.take();
}
}
#[cfg(feature = "properties")]
impl Drop for InterpRedactionScopeGuard {
fn drop(&mut self) {
INTERP_REDACTION.with(|cell| {
let _ = cell.borrow_mut().pop();
});
}
}
#[cfg(feature = "properties")]
#[cold]
#[inline(never)]
pub(crate) fn with_interp_redaction<T>(f: impl FnOnce(&[ScalarRedactionCtx]) -> T) -> T {
INTERP_REDACTION.with(|cell| {
let borrow = cell.borrow();
let pairs = borrow
.last()
.map(|scope| scope.pairs.as_slice())
.unwrap_or(&[]);
f(pairs)
})
}
#[cfg(feature = "properties")]
#[cold]
#[inline(never)]
pub(crate) fn redact_with_ctxs(
mut text: String,
ctxs: &[ScalarRedactionCtx],
fallback: &str,
) -> String {
let mut pairs: Vec<&ScalarRedactionCtx> = ctxs
.iter()
.filter(|ctx| !ctx.effective.is_empty())
.collect();
if pairs.is_empty() {
return fallback.to_owned();
}
pairs.sort_by_key(|ctx| std::cmp::Reverse(ctx.effective.len()));
let mut replaced = false;
for ctx in pairs {
let count = text.matches(&ctx.effective).count();
if count == 1 {
text = text.replace(&ctx.effective, &ctx.raw);
replaced = true;
} else if count > 1 {
return fallback.to_owned();
}
}
if replaced { text } else { fallback.to_owned() }
}
#[cfg(feature = "properties")]
#[cold]
#[inline(never)]
pub(crate) fn redact_custom_message(text: String) -> String {
with_interp_redaction(|ctxs| {
if ctxs.is_empty() {
text
} else {
redact_with_ctxs(text, ctxs, "invalid interpolated scalar value")
}
})
}
#[cfg(not(feature = "properties"))]
pub(crate) fn redact_custom_message(text: String) -> String {
text
}
#[cfg(feature = "properties")]
#[cold]
#[inline(never)]
pub(crate) fn redact_dynamic_value(text: String, fallback: &str) -> String {
with_interp_redaction(|ctxs| {
if ctxs.is_empty() {
text
} else {
redact_with_ctxs(text, ctxs, fallback)
}
})
}
#[cfg(not(feature = "properties"))]
pub(crate) fn redact_dynamic_value(text: String, _: &str) -> String {
text
}
#[cfg(feature = "properties")]
#[cold]
#[inline(never)]
pub(crate) fn redact_dynamic_identifier(text: &str, fallback: &str) -> String {
with_interp_redaction(|ctxs| {
if ctxs.is_empty() {
return text.to_owned();
}
for ctx in ctxs {
if text == ctx.effective {
return ctx.raw.clone();
}
}
fallback.to_owned()
})
}
#[cfg(not(feature = "properties"))]
pub(crate) fn redact_dynamic_identifier(text: &str, _: &str) -> String {
text.to_owned()
}
#[cfg(feature = "properties")]
#[cold]
#[inline(never)]
pub(crate) fn with_interp_redaction_scope<T>(f: impl FnOnce() -> T) -> T {
let _guard = InterpRedactionScopeGuard::new();
f()
}
#[cfg(not(feature = "properties"))]
pub(crate) fn with_interp_redaction_scope<T>(f: impl FnOnce() -> T) -> T {
f()
}