use std::fmt::Display;
use std::fmt::Write;
use qubit_budget::BudgetedStringError;
use qubit_budget::MeasuredBudgetError;
use qubit_budget::ResourceBudget;
use qubit_budget::ResourceQuantity;
use qubit_budget::json::JsonMeasurement;
use qubit_budget::json::JsonValueBudget;
use qubit_budget::json::JsonValueLimits;
use qubit_datatype::ConversionLimits;
use qubit_datatype::ConversionPolicy;
use qubit_datatype::ConversionResource;
use qubit_datatype::ConversionSession;
use qubit_datatype::DataConversionError;
use qubit_datatype::DataType;
use qubit_datatype::InvalidValueReason;
use super::measurement_writer::MeasurementWriter;
use crate::ValueError;
use crate::ValueResult;
pub(super) struct ProjectionBudget<'a> {
pub(super) limits: &'a ConversionLimits,
pub(super) conversion: ConversionSession<'a>,
pub(super) data_type: DataType,
pub(super) source_index: Option<usize>,
items: ResourceBudget<ConversionResource, u64>,
input: ResourceBudget<ConversionResource, u64>,
output: ResourceBudget<ConversionResource, u64>,
structure: JsonValueBudget<ConversionResource, u64>,
}
impl<'a> ProjectionBudget<'a> {
pub(super) fn new(data_type: DataType, policy: &'a ConversionPolicy, limits: &'a ConversionLimits) -> Self {
let operation = limits.operation();
let structure_limits = limits
.structured()
.value()
.structure_limits()
.to_builder()
.nodes_limit(*operation.structured_nodes_limit())
.build();
let structure = JsonValueBudget::new(
JsonValueLimits::builder()
.structure_limits(structure_limits)
.payload_bytes_limit(*operation.structured_payload_bytes_limit())
.build(),
);
Self {
limits,
conversion: ConversionSession::new(policy, limits),
data_type,
source_index: None,
items: ResourceBudget::from_limit(*operation.items_limit()),
input: ResourceBudget::from_limit(*operation.input_bytes_limit()),
output: ResourceBudget::from_limit(*operation.output_bytes_limit()),
structure,
}
}
pub(super) fn error(&self, source: MeasuredBudgetError<ConversionResource, u64>) -> ValueError {
ValueError::JsonProjectionLimit {
data_type: self.data_type,
source_index: self.source_index,
source,
}
}
pub(super) fn item(&mut self) -> ValueResult<()> {
self.items.try_consume(1).map_err(|error| self.error(error.into()))
}
pub(super) fn admit(&mut self, measurement: JsonMeasurement) -> ValueResult<()> {
let result = {
let mut transaction = self.structure.transaction();
transaction.try_admit(measurement).and_then(|()| transaction.commit())
};
result.map_err(|error| self.error(error))
}
pub(super) fn input(&mut self, text: &str) -> ValueResult<()> {
self.input
.try_consume_usize(text.len())
.map_err(|error| self.error(error))
}
pub(super) fn text(&mut self, text: &str, depth: usize, key: bool) -> ValueResult<()> {
self.input(text)?;
if !key {
let amount = u64::try_from_usize(text.len()).map_err(|source| {
self.error(MeasuredBudgetError::quantity(
ConversionResource::StructuredTextBytes,
source,
))
})?;
self.limits
.structured()
.max_text_bytes_limit()
.check(amount)
.map_err(|error| self.error(error.into()))?;
}
let measurement = if key {
JsonMeasurement::Key { bytes: text.len() }
} else {
JsonMeasurement::String {
depth,
bytes: text.len(),
}
};
self.admit(measurement)?;
self.output
.try_consume_usize(text.len())
.map_err(|error| self.error(error))
}
pub(super) fn admit_output_string(&mut self, bytes: usize, depth: usize) -> ValueResult<()> {
let amount = u64::try_from_usize(bytes).map_err(|source| {
self.error(MeasuredBudgetError::quantity(
ConversionResource::StructuredTextBytes,
source,
))
})?;
self.limits
.structured()
.max_text_bytes_limit()
.check(amount)
.map_err(|error| self.error(error.into()))?;
self.admit(JsonMeasurement::String { depth, bytes })?;
self.output.try_consume_usize(bytes).map_err(|error| self.error(error))
}
fn rendering_budget(&mut self, depth: usize, number: bool) -> ValueResult<ResourceBudget<ConversionResource, u64>> {
let probe = if number {
JsonMeasurement::Number { depth, bytes: 0 }
} else {
JsonMeasurement::String { depth, bytes: 0 }
};
let result = self.structure.transaction().try_admit(probe);
result.map_err(|error| self.error(error))?;
let mut rendering = ResourceBudget::from_limit(*self.limits.operation().output_bytes_limit());
rendering
.try_consume(self.output.used())
.map_err(|error| self.error(error.into()))?;
let mut payload = ResourceBudget::from_limit(*self.limits.operation().structured_payload_bytes_limit());
payload
.try_consume(self.structure.used_payload_bytes().unwrap_or(0))
.map_err(|error| self.error(error.into()))?;
let text_limit = ResourceBudget::from_limit(*self.limits.structured().max_text_bytes_limit());
for candidate in [payload, text_limit] {
if candidate.remaining() < rendering.remaining() {
rendering = candidate;
}
}
Ok(rendering)
}
pub(super) fn display<T: Display + ?Sized>(&mut self, value: &T, depth: usize, number: bool) -> ValueResult<()> {
let mut writer = MeasurementWriter::new(self.rendering_budget(depth, number)?);
let formatted = write!(&mut writer, "{value}");
let bytes = writer.finish().map_err(|error| self.error(error))?;
formatted.map_err(|_| self.format_error())?;
let bytes = usize::try_from_u64(bytes)
.map_err(|source| self.error(MeasuredBudgetError::quantity(ConversionResource::OutputBytes, source)))?;
self.admit(if number {
JsonMeasurement::Number { depth, bytes }
} else {
JsonMeasurement::String { depth, bytes }
})?;
self.output.try_consume_usize(bytes).map_err(|error| self.error(error))
}
pub(super) fn format<T: Display + ?Sized>(&mut self, value: &T, depth: usize) -> ValueResult<String> {
let text = self
.rendering_budget(depth, false)?
.try_write_string(|writer| write!(writer.as_fmt(), "{value}"))
.map_err(|error| match error {
BudgetedStringError::Budget(error) => self.error(error.into()),
BudgetedStringError::Quantity { resource, source } => {
self.error(MeasuredBudgetError::quantity(resource, source))
}
_ => self.format_error(),
})?;
self.admit_output_string(text.len(), depth)?;
Ok(text)
}
fn format_error(&self) -> ValueError {
ValueError::Conversion(DataConversionError::invalid(
self.data_type,
DataType::Json,
InvalidValueReason::OutOfRange,
))
}
}