use std::borrow::Cow;
use std::fmt::{
self,
Write,
};
use super::internal::BoundedMaskWriter;
#[must_use]
#[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]
pub fn fixed(replacement: &str) -> Self {
Self::Fixed {
replacement: replacement.to_string(),
}
}
#[inline]
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]
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,
}
}
pub const fn empty() -> Self {
Self::Empty
}
#[must_use = "use the returned masked value instead of the original value"]
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()),
}
}
#[must_use = "use the opaque replacement instead of formatting the original value"]
#[inline(always)]
pub fn opaque_mask(&self) -> &str {
match self {
Self::Fixed { replacement }
| Self::PreserveEdges { replacement, .. }
| Self::PreserveSuffix { replacement, .. } => replacement,
Self::Empty => "",
}
}
pub(crate) fn mask_bounded<'a>(
&self,
value: &'a str,
max_bytes: usize,
) -> Cow<'a, str> {
if value.is_empty() {
return Cow::Borrowed(value);
}
let mut writer = BoundedMaskWriter::new(max_bytes);
let _ = self.write_masked(value, &mut writer);
Cow::Owned(writer.finish())
}
#[must_use = "use the bounded opaque replacement instead of the original value"]
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()
}
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 = "use the returned masked value instead of the original value"]
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 = "use the returned masked value instead of the original value"]
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)
}