qubit-redact 0.9.0

Rule-driven redaction for fields, diagnostics, HTTP data, and Rust domain objects
Documentation
// =============================================================================
//    Copyright (c) 2026 Haixing Hu.
//
//    SPDX-License-Identifier: Apache-2.0
//
//    Licensed under the Apache License, Version 2.0.
// =============================================================================
//! Runtime-owned byte sink for incrementally serialized safe text.

use std::io;
use std::io::Write;

/// Bounds serializer output before it becomes an unpublished operation.
pub(crate) struct OperationByteSink {
    /// Serializer bytes retained only after complete-token admission.
    output: Vec<u8>,
    /// Maximum bytes the operation may retain.
    maximum: usize,
}

impl OperationByteSink {
    /// Creates an empty byte sink with one operation's output allowance.
    ///
    /// # Parameters
    ///
    /// - `maximum`: Maximum serializer bytes retained for this operation.
    ///
    /// # Returns
    ///
    /// An empty in-memory byte sink.
    #[must_use]
    #[inline(always)]
    pub(crate) const fn new(maximum: usize) -> Self {
        Self {
            output: Vec::new(),
            maximum,
        }
    }

    /// Converts accepted serializer bytes into UTF-8 text.
    ///
    /// # Returns
    ///
    /// `Some(text)` contains valid UTF-8; `None` means retained bytes were
    /// not a complete UTF-8 string.
    #[must_use]
    #[inline(always)]
    pub(crate) fn into_string(self) -> Option<String> {
        String::from_utf8(self.output).ok()
    }
}

impl Write for OperationByteSink {
    /// Atomically appends `buffer` when the complete serializer token fits.
    ///
    /// # Errors
    ///
    /// Returns [`io::ErrorKind::WriteZero`] if the whole buffer cannot fit.
    ///
    /// # Parameters
    ///
    /// - `buffer`: One serializer write, accepted or rejected as a whole.
    ///
    /// # Returns
    ///
    /// The complete buffer length after successful retention.
    #[inline]
    fn write(&mut self, buffer: &[u8]) -> io::Result<usize> {
        if self.output.len().saturating_add(buffer.len()) > self.maximum {
            return Err(io::Error::from(io::ErrorKind::WriteZero));
        }
        self.output.extend_from_slice(buffer);
        Ok(buffer.len())
    }

    /// Returns success without external I/O; in-memory flushing cannot fail.
    #[inline(always)]
    fn flush(&mut self) -> io::Result<()> {
        Ok(())
    }
}