static TRUNCATED_RECORDS: std::sync::atomic::AtomicU64 =
std::sync::atomic::AtomicU64::new(0);
#[must_use]
pub fn truncated_records() -> u64 {
TRUNCATED_RECORDS.load(std::sync::atomic::Ordering::Relaxed)
}
pub fn note_truncated_record() {
TRUNCATED_RECORDS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
}
pub fn apply_string_limits(v: &mut Value, shape: &OutputShape) -> bool {
let mut changed = false;
let Some(obj) = v.as_object_mut() else {
if let Value::String(s) = v {
changed |= truncate_string(s, shape.max_output_bytes);
}
return changed;
};
for (key, len) in &shape.truncate_specs {
if let Some(Value::String(s)) = obj.get_mut(key) {
changed |= prefix_chars(s, *len);
}
}
if let Some(max) = shape.max_output_bytes {
for val in obj.values_mut() {
if let Value::String(s) = val {
changed |= truncate_string(s, Some(max));
}
}
}
changed
}
fn prefix_chars(s: &mut String, len: usize) -> bool {
if s.chars().count() <= len {
return false;
}
let end = s.char_indices().nth(len).map(|(i, _)| i).unwrap_or(s.len());
s.truncate(end);
true
}
fn truncate_string(s: &mut String, max: Option<usize>) -> bool {
let Some(max) = max else {
return false;
};
if s.len() <= max {
return false;
}
let mut end = max;
while end > 0 && !s.is_char_boundary(end) {
end -= 1;
}
s.truncate(end);
true
}
pub fn redact_secrets(v: &mut Value) -> usize {
fn looks_secret(s: &str) -> bool {
SECRET_NEEDLES
.iter()
.any(|n| memmem::find(s.as_bytes(), n.as_bytes()).is_some())
}
fn key_marks_secret(key: &str) -> bool {
let lower = key.to_ascii_lowercase();
SECRET_KEY_MARKERS
.iter()
.any(|m| lower.ends_with(m) || lower.contains(m))
}
match v {
Value::Object(map) => {
let mut count = 0;
for (key, val) in map.iter_mut() {
match val {
Value::String(s) => {
if key_marks_secret(key) || looks_secret(s) {
*s = REDACTED.to_string();
count += 1;
}
}
other => count += redact_secrets(other),
}
}
count
}
Value::Array(items) => items.iter_mut().map(redact_secrets).sum(),
Value::String(s) => {
if looks_secret(s) {
*s = REDACTED.to_string();
1
} else {
0
}
}
_ => 0,
}
}
pub fn normalize_encoding(v: &mut Value) -> bool {
use unicode_normalization::UnicodeNormalization;
let mut changed = false;
match v {
Value::Object(map) => {
for val in map.values_mut() {
changed |= normalize_encoding(val);
}
}
Value::Array(items) => {
for item in items.iter_mut() {
changed |= normalize_encoding(item);
}
}
Value::String(s) => {
if !s.is_ascii() {
let normalized: String = s.nfc().collect();
if normalized != *s {
changed = true;
*s = normalized;
}
}
}
_ => {}
}
changed
}