use super::JsonValueBudget;
use super::internal::JsonValueState;
use super::internal::PreparedJsonAdmission;
use crate::json::JsonContainerKind;
use crate::json::JsonMeasurement;
use crate::resource::InsufficientBudgetError;
use crate::resource::MeasuredBudgetError;
use crate::resource::ResourceQuantity;
pub struct JsonValueTransaction<'a, R, Q>
where
Q: ResourceQuantity,
{
target: &'a mut JsonValueBudget<R, Q>,
working: JsonValueState<Q>,
failure: Option<MeasuredBudgetError<R, Q>>,
}
impl<'a, R, Q> JsonValueTransaction<'a, R, Q>
where
R: Clone,
Q: ResourceQuantity,
{
#[inline(always)]
pub(super) const fn new(target: &'a mut JsonValueBudget<R, Q>) -> Self {
Self {
working: target.state,
failure: None,
target,
}
}
#[must_use]
#[inline(always)]
pub const fn has_limits(&self) -> bool {
self.target.limits().has_limits()
}
pub fn try_admit(&mut self, measurement: JsonMeasurement) -> Result<(), MeasuredBudgetError<R, Q>> {
if let Some(error) = &self.failure {
return Err(error.clone());
}
let result: Result<PreparedJsonAdmission<Q>, MeasuredBudgetError<R, Q>> = (|| {
let prepared = PreparedJsonAdmission::prepare(self.target.limits(), measurement)?;
prepared.check_point(self.target.limits())?;
self.check_cumulative(prepared)?;
Ok(prepared)
})();
match result {
Ok(prepared) => {
self.apply(prepared);
Ok(())
}
Err(error) => {
self.failure = Some(error.clone());
Err(error)
}
}
}
pub fn try_enter_container(
&mut self,
kind: JsonContainerKind,
depth: usize,
) -> Result<(), MeasuredBudgetError<R, Q>> {
let measurement = match kind {
JsonContainerKind::Sequence => JsonMeasurement::Array { depth, items: 0 },
JsonContainerKind::Map => JsonMeasurement::Object { depth, entries: 0 },
};
self.try_admit(measurement)
}
pub fn check_container_count(
&mut self,
kind: JsonContainerKind,
prospective: usize,
) -> Result<(), MeasuredBudgetError<R, Q>> {
let limit = match kind {
JsonContainerKind::Sequence => self.target.limits().structure_limits().sequence_items_limit(),
JsonContainerKind::Map => self.target.limits().structure_limits().map_entries_limit(),
};
if let Some(error) = &self.failure {
return Err(error.clone());
}
let result = self.check_container_items(prospective, limit);
if let Err(error) = &result {
self.failure = Some(error.clone());
}
result
}
pub fn commit(self) -> Result<(), MeasuredBudgetError<R, Q>> {
if let Some(error) = self.failure {
return Err(error);
}
self.target.state = self.working;
Ok(())
}
#[must_use]
#[inline]
pub fn used_nodes(&self) -> Option<Q> {
self.working
.remaining_nodes()
.zip(self.target.limits().max_nodes())
.map(|(remaining, maximum)| maximum - remaining)
}
#[must_use]
#[inline(always)]
pub const fn remaining_nodes(&self) -> Option<Q> {
self.working.remaining_nodes()
}
#[must_use]
#[inline]
pub fn used_payload_bytes(&self) -> Option<Q> {
self.working
.remaining_payload_bytes()
.zip(self.target.limits().max_payload_bytes())
.map(|(remaining, maximum)| maximum - remaining)
}
#[must_use]
#[inline(always)]
pub const fn remaining_payload_bytes(&self) -> Option<Q> {
self.working.remaining_payload_bytes()
}
fn check_cumulative(&self, prepared: PreparedJsonAdmission<Q>) -> Result<(), MeasuredBudgetError<R, Q>> {
let (node, payload_bytes) = cumulative_cost(prepared);
if node {
self.check_nodes()?;
}
self.check_payload(payload_bytes)
}
fn check_container_items(
&self,
amount: usize,
limit: Option<&crate::ResourceLimit<R, Q>>,
) -> Result<(), MeasuredBudgetError<R, Q>> {
let Some(limit) = limit else {
return Ok(());
};
let amount = Q::try_from_usize(amount)
.map_err(|source| MeasuredBudgetError::quantity(limit.resource().clone(), source))?;
limit.check(amount).map_err(MeasuredBudgetError::from)
}
fn check_nodes(&self) -> Result<(), MeasuredBudgetError<R, Q>> {
let Some(remaining) = self.working.remaining_nodes() else {
return Ok(());
};
if Q::ONE <= remaining {
return Ok(());
}
let Some(limit) = self.target.limits().structure_limits().nodes_limit() else {
return Ok(());
};
Err(InsufficientBudgetError {
resource: limit.resource().clone(),
limit: limit.maximum(),
remaining,
requested: Q::ONE,
}
.into())
}
fn check_payload(&self, payload_bytes: Q) -> Result<(), MeasuredBudgetError<R, Q>> {
let Some(remaining) = self.working.remaining_payload_bytes() else {
return Ok(());
};
if payload_bytes <= remaining {
return Ok(());
}
let Some(limit) = self.target.limits().payload_bytes_limit() else {
return Ok(());
};
Err(InsufficientBudgetError {
resource: limit.resource().clone(),
limit: limit.maximum(),
remaining,
requested: payload_bytes,
}
.into())
}
fn apply(&mut self, prepared: PreparedJsonAdmission<Q>) {
let (node, payload_bytes) = cumulative_cost(prepared);
self.working.apply(node, payload_bytes);
}
}
fn cumulative_cost<Q>(prepared: PreparedJsonAdmission<Q>) -> (bool, Q)
where
Q: ResourceQuantity,
{
match prepared {
PreparedJsonAdmission::Null { .. }
| PreparedJsonAdmission::Boolean { .. }
| PreparedJsonAdmission::Array { .. }
| PreparedJsonAdmission::Object { .. } => (true, Q::ZERO),
PreparedJsonAdmission::String { bytes, .. } | PreparedJsonAdmission::Number { bytes, .. } => (true, bytes),
PreparedJsonAdmission::Key { bytes } => (false, bytes),
}
}