From 3d104d80ed18bd48a67980139a0c46e4991b4680 Mon Sep 17 00:00:00 2001 From: RA <70325462+RAprogramm@users.noreply.github.com> Date: Fri, 26 Sep 2025 17:14:41 +0700 Subject: [PATCH] Optimize last4 masking iterator --- src/response/problem_json.rs | 43 +++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/src/response/problem_json.rs b/src/response/problem_json.rs index 76e9411..3842a8d 100644 --- a/src/response/problem_json.rs +++ b/src/response/problem_json.rs @@ -1,8 +1,7 @@ use alloc::{ borrow::Cow, collections::BTreeMap, - string::{String, ToString}, - vec::Vec + string::{String, ToString} }; use core::{fmt::Write, net::IpAddr}; @@ -598,8 +597,8 @@ fn mask_last4_field_value(value: &FieldValue) -> Option { } fn mask_last4(value: &str) -> String { - let chars: Vec = value.chars().collect(); - let total = chars.len(); + let chars = value.chars(); + let total = chars.clone().count(); if total == 0 { return String::new(); } @@ -607,12 +606,8 @@ fn mask_last4(value: &str) -> String { let keep = if total <= 4 { 1 } else { 4 }; let mask_len = total.saturating_sub(keep); let mut masked = String::with_capacity(value.len()); - for _ in 0..mask_len { - masked.push('*'); - } - for ch in chars.iter().skip(mask_len) { - masked.push(*ch); - } + masked.extend(core::iter::repeat_n('*', mask_len)); + masked.extend(chars.skip(mask_len)); masked } @@ -1080,6 +1075,34 @@ mod tests { } } + #[test] + fn last4_metadata_handles_multibyte_suffix() { + let multibyte = "💳💳💳💳💳💳"; + let err = AppError::internal("oops").with_field( + crate::field::str("emoji", multibyte).with_redaction(FieldRedaction::Last4) + ); + let problem = ProblemJson::from_ref(&err); + let metadata = problem.metadata.expect("metadata"); + let value = metadata.0.get("emoji").expect("emoji field"); + match value { + ProblemMetadataValue::String(text) => { + let total = multibyte.chars().count(); + let keep = if total <= 4 { 1 } else { 4 }; + let expected_mask_len = total.saturating_sub(keep); + let expected_suffix: String = multibyte.chars().skip(expected_mask_len).collect(); + + assert!(text.ends_with(&expected_suffix)); + assert!(text.chars().take(expected_mask_len).all(|c| c == '*')); + assert_eq!( + text.chars().filter(|c| *c == '*').count(), + expected_mask_len + ); + assert_eq!(text.chars().count(), multibyte.chars().count()); + } + other => panic!("unexpected metadata value: {other:?}") + } + } + #[test] fn problem_json_serialization_masks_sensitive_metadata() { let secret = "super-secret";