use std::fmt;
use std::io;
use super::BudgetedStringError;
use super::internal::FmtWriter;
use super::internal::IoWriter;
use super::internal::WriterFailure;
use crate::resource::ResourceBudget;
use crate::resource::ResourceQuantity;
pub struct BudgetedStringWriter<'a, R, Q = u64>
where
Q: ResourceQuantity,
{
budget: &'a ResourceBudget<R, Q>,
output: Vec<u8>,
failure: Option<WriterFailure<R, Q>>,
}
impl<'a, R, Q> BudgetedStringWriter<'a, R, Q>
where
R: Clone,
Q: ResourceQuantity,
{
fn new(budget: &'a ResourceBudget<R, Q>) -> Self {
Self {
budget,
output: Vec::new(),
failure: None,
}
}
fn into_parts(self) -> (Vec<u8>, Option<WriterFailure<R, Q>>) {
(self.output, self.failure)
}
pub(crate) fn append(&mut self, bytes: &[u8]) -> bool {
if self.failure.is_some() {
return false;
}
let Some(next_len) = checked_output_len(self.output.len(), bytes.len()) else {
self.failure = Some(WriterFailure::LengthOverflow);
return false;
};
let next_length = match Q::try_from_usize(next_len) {
Ok(value) => value,
Err(source) => {
self.failure = Some(WriterFailure::Quantity {
resource: self.budget.resource().clone(),
source,
});
return false;
}
};
if let Err(error) = self.budget.check_available(next_length) {
self.failure = Some(WriterFailure::Budget(error));
return false;
}
if next_len > self.output.capacity() {
let target = self.output.capacity().saturating_mul(2).max(next_len);
if let Err(source) = self.output.try_reserve_exact(target.saturating_sub(self.output.len())) {
self.failure = Some(WriterFailure::Allocation(source));
return false;
}
}
self.output.extend_from_slice(bytes);
true
}
#[must_use]
#[inline]
pub fn as_fmt(&mut self) -> impl fmt::Write + '_ {
FmtWriter { writer: self }
}
#[must_use]
#[inline]
pub fn as_io(&mut self) -> impl io::Write + '_ {
IoWriter { writer: self }
}
}
const fn checked_output_len(current: usize, additional: usize) -> Option<usize> {
current.checked_add(additional)
}
pub(crate) fn render_budgeted_string<R, Q, E, F>(
budget: &mut ResourceBudget<R, Q>,
render: F,
) -> Result<String, BudgetedStringError<R, E, Q>>
where
R: Clone + fmt::Debug,
Q: ResourceQuantity,
E: fmt::Debug + fmt::Display,
F: FnOnce(&mut BudgetedStringWriter<'_, R, Q>) -> Result<(), E>,
{
let mut writer = BudgetedStringWriter::new(budget);
let rendered = render(&mut writer);
let (bytes, failure) = writer.into_parts();
match failure {
Some(WriterFailure::Budget(error)) => {
return Err(BudgetedStringError::Budget(error));
}
Some(WriterFailure::Quantity { resource, source }) => {
return Err(BudgetedStringError::Quantity { resource, source });
}
Some(WriterFailure::LengthOverflow) => {
return Err(BudgetedStringError::LengthOverflow);
}
Some(WriterFailure::Allocation(source)) => {
return Err(BudgetedStringError::Allocation(source));
}
None => {}
}
if let Err(error) = rendered {
return Err(BudgetedStringError::Render(error));
}
let output = String::from_utf8(bytes).map_err(BudgetedStringError::InvalidUtf8)?;
let output_length = Q::try_from_usize(output.len()).map_err(|source| BudgetedStringError::Quantity {
resource: budget.resource().clone(),
source,
})?;
budget.try_consume(output_length).map_err(BudgetedStringError::Budget)?;
Ok(output)
}