use std::fmt::Debug;
use std::fmt::Write as _;
use crate::Sensitivity;
use crate::domain::RedactionEntries;
use crate::domain::RedactionFields;
use crate::domain::RedactionItems;
use crate::domain::internal::bounded_capture::bounded_debug;
use crate::domain::internal::bounded_debug_writer::BoundedDebugWriter;
use crate::runtime::runtime_session::RuntimeSession;
pub struct RedactionWriter<'session> {
pub(super) session: &'session mut dyn RuntimeSession,
}
impl<'session> RedactionWriter<'session> {
#[must_use]
#[inline(always)]
pub(crate) fn new(session: &'session mut dyn RuntimeSession) -> Self {
Self { session }
}
#[must_use]
#[inline(always)]
pub(crate) fn new_root(session: &'session mut dyn RuntimeSession) -> Self {
Self::new(session)
}
#[inline]
pub fn literal(&mut self, text: &'static str) {
if self.session.domain_frame_is_truncated() {
return;
}
self.write_fragment(text);
}
pub fn unredacted<T>(&mut self, value: &T) -> &mut Self
where
T: Debug + ?Sized,
{
if self.session.domain_frame_is_truncated() {
return self;
}
if self.session.is_inspection() {
return self;
}
self.write_debug(value);
self
}
#[inline(always)]
pub fn unmarked<T>(&mut self, value: &T) -> &mut Self
where
T: Debug + ?Sized,
{
self.unredacted(value)
}
#[inline(always)]
pub fn record<F>(&mut self, name: &'static str, configure: F)
where
F: for<'writer> FnOnce(&mut RedactionFields<'writer, 'session>),
{
self.write_field_structure(name, " { ", " }", configure);
}
#[inline(always)]
pub fn tuple<F>(&mut self, name: &'static str, configure: F)
where
F: for<'writer> FnOnce(&mut RedactionFields<'writer, 'session>),
{
self.write_field_structure(name, "(", ")", configure);
}
#[inline]
pub fn transparent<F>(&mut self, configure: F)
where
F: for<'writer> FnOnce(&mut RedactionFields<'writer, 'session>),
{
let mut fields = RedactionFields {
writer: self,
named: false,
};
configure(&mut fields);
self.trim_trailing_separator();
}
#[inline(always)]
pub fn sequence<F>(&mut self, configure: F)
where
F: for<'writer> FnOnce(&mut RedactionItems<'writer, 'session>),
{
self.write_item_structure("", "[", "]", configure);
}
#[inline(always)]
pub fn map<F>(&mut self, configure: F)
where
F: for<'writer> FnOnce(&mut RedactionEntries<'writer, 'session>),
{
self.write_entry_structure("", "{ ", " }", configure);
}
#[inline]
pub fn variant<F>(&mut self, enum_name: &'static str, variant_name: &'static str, configure: F)
where
F: for<'writer> FnOnce(&mut RedactionFields<'writer, 'session>),
{
self.write_fragment(enum_name);
self.write_fragment("::");
self.write_field_structure(variant_name, " { ", " }", configure);
}
#[must_use]
#[inline]
pub(super) fn can_write(&self) -> bool {
!self.session.domain_frame_is_truncated() && self.remaining_output_bytes() > 0
}
#[must_use]
#[inline(always)]
pub(super) fn remaining_output_bytes(&self) -> usize {
self.session.remaining_domain_frame_output_bytes()
}
#[inline(always)]
pub(crate) fn trim_trailing_separator(&mut self) {
self.session.trim_domain_frame_separator();
}
#[cfg(feature = "json")]
pub(super) fn write_json_text(&mut self, value: &str) {
if self.session.is_inspection() {
crate::formats::json::inspection::inspect_text(self.session, value);
return;
}
if !self.session.admit_input(value.len()) {
self.truncate_without_output_limit();
return;
}
let admitted = match crate::formats::json::admit_json_text_value(self.session, value) {
Ok(value) => value,
Err(crate::formats::json::JsonAdmissionError::Invalid) => {
let allowance = self.session.remaining_output_bytes().min(self.remaining_output_bytes());
let output = crate::formats::json::invalid_json_output(self.session.policy(), allowance);
self.session.record_rendered_provenance(&output);
self.write_debug(output.text());
return;
}
Err(crate::formats::json::JsonAdmissionError::Limit) => {
self.truncate_without_output_limit();
return;
}
};
let allowance = self.session.remaining_output_bytes().min(self.remaining_output_bytes());
let output = crate::formats::json::redact_json_value_with_limit(self.session.policy(), &admitted, allowance);
if output.completion() != crate::RedactionCompletion::Complete {
self.truncate_without_output_limit();
}
self.session.record_rendered_provenance(&output);
self.write_debug(output.text());
}
#[cfg(feature = "json")]
pub(super) fn write_json_value(&mut self, value: &serde_json::Value) {
if self.session.is_inspection() {
crate::formats::json::inspection::inspect_borrowed_value(self.session, value);
return;
}
if !self.session.admit_json_value(value) {
self.truncate_without_output_limit();
return;
}
let allowance = self.session.remaining_output_bytes().min(self.remaining_output_bytes());
let output = crate::formats::json::redact_json_value_with_limit(self.session.policy(), value, allowance);
if output.completion() != crate::RedactionCompletion::Complete {
self.truncate_without_output_limit();
}
self.session.record_rendered_provenance(&output);
self.write_fragment(output.text());
}
#[must_use]
#[inline(always)]
pub(crate) fn finish_with_completion(self) -> (String, bool, bool) {
self.session.finish_domain_frame()
}
#[inline]
pub(super) fn truncate_for_output_limit(&mut self) {
self.session.mark_domain_frame_output_limit_reached();
self.truncate_without_output_limit();
}
#[inline(always)]
pub(super) fn truncate_without_output_limit(&mut self) {
self.session.truncate_domain_frame_without_output_limit();
}
#[inline(always)]
pub(super) fn write_fragment(&mut self, text: &str) -> bool {
self.session.write_domain_fragment(text)
}
#[inline]
pub(crate) fn write_debug<T>(&mut self, value: &T)
where
T: Debug + ?Sized,
{
if self.session.is_inspection() {
return;
}
let mut formatter = BoundedDebugWriter { writer: self };
let _ = write!(&mut formatter, "{value:?}");
}
pub(super) fn write_masked_debug<T>(&mut self, level: Sensitivity, value: &T)
where
T: Debug + ?Sized,
{
if self.session.is_inspection() {
self.session.observe_sensitivity(level);
return;
}
if matches!(level, Sensitivity::High | Sensitivity::Secret) {
let masked = self
.session
.policy()
.masking()
.mask_opaque_bounded(level, self.remaining_output_bytes());
self.write_debug(&masked);
return;
}
let raw_limit = self.remaining_output_bytes();
let (raw, raw_truncated) = bounded_debug(value, raw_limit);
let (masked, mask_truncated) =
self.session
.policy()
.masking()
.mask_bounded_with_truncation(level, &raw, self.remaining_output_bytes());
self.write_debug(masked.as_ref());
if raw_truncated || mask_truncated {
self.truncate_for_output_limit();
}
}
#[inline]
pub(crate) fn write_level_scalar<T>(&mut self, level: Sensitivity, value: &T)
where
T: Debug + ?Sized,
{
if self.session.policy().is_disabled() {
self.write_debug(value);
} else {
self.write_masked_debug(level, value);
}
}
#[inline(always)]
pub(crate) fn level_tuple<F>(&mut self, configure: F)
where
F: for<'writer> FnOnce(&mut RedactionItems<'writer, 'session>),
{
self.write_item_structure("", "(", ")", configure);
}
fn write_field_structure<F>(
&mut self,
name: &'static str,
opening: &'static str,
closing: &'static str,
configure: F,
) where
F: for<'writer> FnOnce(&mut RedactionFields<'writer, 'session>),
{
if !self.session.begin_domain_value() {
self.truncate_without_output_limit();
return;
}
self.write_fragment(name);
self.write_fragment(opening);
if self.can_write() {
let mut fields = RedactionFields {
writer: self,
named: opening == " { ",
};
configure(&mut fields);
}
if self.can_write() {
self.trim_trailing_separator();
self.write_fragment(closing);
}
self.session.leave_domain_value();
}
fn write_item_structure<F>(
&mut self,
name: &'static str,
opening: &'static str,
closing: &'static str,
configure: F,
) where
F: for<'writer> FnOnce(&mut RedactionItems<'writer, 'session>),
{
if !self.session.begin_domain_value() {
self.truncate_without_output_limit();
return;
}
self.write_fragment(name);
self.write_fragment(opening);
if self.can_write() {
configure(&mut RedactionItems {
writer: self,
admitted_item: false,
});
}
if self.can_write() {
self.trim_trailing_separator();
self.write_fragment(closing);
}
self.session.leave_domain_value();
}
fn write_entry_structure<F>(
&mut self,
name: &'static str,
opening: &'static str,
closing: &'static str,
configure: F,
) where
F: for<'writer> FnOnce(&mut RedactionEntries<'writer, 'session>),
{
if !self.session.begin_domain_value() {
self.truncate_without_output_limit();
return;
}
self.write_fragment(name);
self.write_fragment(opening);
if self.can_write() {
configure(&mut RedactionEntries {
writer: self,
admitted_entry: false,
});
}
if self.can_write() {
self.trim_trailing_separator();
self.write_fragment(closing);
}
self.session.leave_domain_value();
}
}