use std::borrow::Cow;
use std::fmt;
use std::fmt::Write;
use crate::policy::internal::BoundedMaskWriter;
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MaskPolicy {
#[non_exhaustive]
Fixed {
replacement: String,
},
#[non_exhaustive]
PreserveEdges {
prefix_chars: usize,
suffix_chars: usize,
replacement: String,
full_mask_below_or_equal: usize,
},
#[non_exhaustive]
PreserveSuffix {
suffix_chars: usize,
replacement: String,
full_mask_below_or_equal: usize,
},
Empty,
}
impl MaskPolicy {
#[inline]
#[must_use]
pub fn fixed(replacement: &str) -> Self {
Self::Fixed {
replacement: replacement.to_string(),
}
}
#[inline]
#[must_use]
pub fn preserve_edges(
prefix_chars: usize,
suffix_chars: usize,
replacement: &str,
full_mask_below_or_equal: usize,
) -> Self {
Self::PreserveEdges {
prefix_chars,
suffix_chars,
replacement: replacement.to_string(),
full_mask_below_or_equal,
}
}
#[inline]
#[must_use]
pub fn preserve_suffix(suffix_chars: usize, replacement: &str, full_mask_below_or_equal: usize) -> Self {
Self::PreserveSuffix {
suffix_chars,
replacement: replacement.to_string(),
full_mask_below_or_equal,
}
}
#[must_use]
#[inline(always)]
pub const fn empty() -> Self {
Self::Empty
}
#[must_use]
#[inline(always)]
pub fn opaque_mask(&self) -> &str {
match self {
Self::Fixed { replacement }
| Self::PreserveEdges { replacement, .. }
| Self::PreserveSuffix { replacement, .. } => replacement,
Self::Empty => "",
}
}
#[must_use]
pub fn mask<'a>(&self, value: &'a str) -> Cow<'a, str> {
if value.is_empty() {
return Cow::Borrowed(value);
}
match self {
Self::Fixed { replacement } => Cow::Owned(replacement.clone()),
Self::PreserveEdges {
prefix_chars,
suffix_chars,
replacement,
full_mask_below_or_equal,
} => Cow::Owned(mask_preserving_edges(
value,
*prefix_chars,
*suffix_chars,
replacement,
*full_mask_below_or_equal,
)),
Self::PreserveSuffix {
suffix_chars,
replacement,
full_mask_below_or_equal,
} => Cow::Owned(mask_preserving_suffix(
value,
*suffix_chars,
replacement,
*full_mask_below_or_equal,
)),
Self::Empty => Cow::Owned(String::new()),
}
}
#[cfg(feature = "http")]
#[must_use]
#[inline(always)]
pub(crate) fn mask_bounded<'a>(&self, value: &'a str, max_bytes: usize) -> Cow<'a, str> {
self.mask_bounded_with_truncation(value, max_bytes).0
}
#[must_use]
pub(crate) fn mask_bounded_with_truncation<'a>(&self, value: &'a str, max_bytes: usize) -> (Cow<'a, str>, bool) {
if value.is_empty() {
return (Cow::Borrowed(value), false);
}
let mut writer = BoundedMaskWriter::new(max_bytes);
let _ = self.write_masked(value, &mut writer);
let (output, truncated) = writer.finish();
(Cow::Owned(output), truncated)
}
#[must_use]
pub(crate) fn opaque_mask_bounded(&self, max_bytes: usize) -> String {
let mut writer = BoundedMaskWriter::new(max_bytes);
let _ = writer.write_str(self.opaque_mask());
writer.finish().0
}
pub(crate) fn write_masked<W: fmt::Write>(&self, value: &str, writer: &mut W) -> fmt::Result {
match self {
Self::Fixed { replacement } => writer.write_str(replacement),
Self::PreserveEdges {
prefix_chars,
suffix_chars,
replacement,
full_mask_below_or_equal,
} => {
let Some((prefix_end, suffix_start)) =
preserved_edge_bounds(value, *prefix_chars, *suffix_chars, *full_mask_below_or_equal)
else {
return writer.write_str(replacement);
};
writer.write_str(&value[..prefix_end])?;
writer.write_str(replacement)?;
writer.write_str(&value[suffix_start..])
}
Self::PreserveSuffix {
suffix_chars,
replacement,
full_mask_below_or_equal,
} => {
let Some(suffix_start) = preserved_suffix_start(value, *suffix_chars, *full_mask_below_or_equal) else {
return writer.write_str(replacement);
};
writer.write_str(replacement)?;
writer.write_str(&value[suffix_start..])
}
Self::Empty => Ok(()),
}
}
}
#[must_use]
fn mask_preserving_edges(
value: &str,
prefix_chars: usize,
suffix_chars: usize,
replacement: &str,
full_mask_below_or_equal: usize,
) -> String {
let Some((prefix_end, suffix_start)) =
preserved_edge_bounds(value, prefix_chars, suffix_chars, full_mask_below_or_equal)
else {
return replacement.to_string();
};
let mut masked = String::with_capacity(prefix_end + replacement.len() + value.len() - suffix_start);
masked.push_str(&value[..prefix_end]);
masked.push_str(replacement);
masked.push_str(&value[suffix_start..]);
masked
}
#[must_use]
fn mask_preserving_suffix(
value: &str,
suffix_chars: usize,
replacement: &str,
full_mask_below_or_equal: usize,
) -> String {
let Some(suffix_start) = preserved_suffix_start(value, suffix_chars, full_mask_below_or_equal) else {
return replacement.to_string();
};
let mut masked = String::with_capacity(replacement.len() + value.len() - suffix_start);
masked.push_str(replacement);
masked.push_str(&value[suffix_start..]);
masked
}
fn preserved_edge_bounds(
value: &str,
prefix_chars: usize,
suffix_chars: usize,
full_mask_below_or_equal: usize,
) -> Option<(usize, usize)> {
let edge_chars = prefix_chars.checked_add(suffix_chars)?;
let required_chars = full_mask_below_or_equal.max(edge_chars);
value.chars().nth(required_chars)?;
let prefix_end = value.char_indices().nth(prefix_chars)?.0;
let suffix_start = suffix_start(value, suffix_chars)?;
Some((prefix_end, suffix_start))
}
fn preserved_suffix_start(value: &str, suffix_chars: usize, full_mask_below_or_equal: usize) -> Option<usize> {
let required_chars = full_mask_below_or_equal.max(suffix_chars);
value.chars().nth(required_chars)?;
suffix_start(value, suffix_chars)
}
fn suffix_start(value: &str, suffix_chars: usize) -> Option<usize> {
if suffix_chars == 0 {
return Some(value.len());
}
value.char_indices().rev().nth(suffix_chars - 1).map(|(index, _)| index)
}