use qubit_budget::BudgetError;
use qubit_budget::BudgetedStringError;
use qubit_budget::MeasuredBudgetError;
use qubit_budget::ResourceLimit;
use qubit_budget::ResourceQuantity;
#[cfg(feature = "json")]
use qubit_budget::json::JsonDecodeSession;
#[cfg(feature = "json")]
use qubit_budget::json::JsonEncodeSession;
use qubit_budget::json::JsonValueTransaction;
#[cfg(feature = "json")]
use qubit_json::decode::JsonDecodeError;
#[cfg(feature = "json")]
use qubit_json::decode::JsonDecoder;
#[cfg(feature = "json")]
use qubit_json::encode::JsonEncodeError;
#[cfg(feature = "json")]
use qubit_json::encode::JsonEncoder;
#[cfg(feature = "json")]
use qubit_json::value::traverse::JsonTreeReader;
#[cfg(feature = "json")]
use serde::Deserialize;
#[cfg(feature = "json")]
use serde::Serialize;
#[cfg(feature = "json")]
use serde::de::DeserializeSeed;
#[cfg(feature = "json")]
use serde_json::Value;
use super::admitted_scalar_source::AdmittedScalarSource;
use super::conversion_resource::ConversionResource;
use super::conversion_string_writer::ConversionStringWriter;
use super::error::DataConversionError;
use super::internal::ConversionBudget;
use super::options::ConversionLimits;
use super::options::ConversionPolicy;
use crate::datatype::DataType;
#[must_use]
#[derive(Debug, PartialEq, Eq)]
pub struct ConversionSession<'a> {
policy: &'a ConversionPolicy,
limits: &'a ConversionLimits,
budget: ConversionBudget,
}
impl<'a> ConversionSession<'a> {
#[inline]
pub fn new(policy: &'a ConversionPolicy, limits: &'a ConversionLimits) -> Self {
Self {
policy,
limits,
budget: ConversionBudget::new(limits),
}
}
#[inline(always)]
pub const fn policy(&self) -> &'a ConversionPolicy {
self.policy
}
#[inline(always)]
pub const fn limits(&self) -> &'a ConversionLimits {
self.limits
}
#[cfg(feature = "json")]
pub(crate) fn decode_json<'de, T>(
&mut self,
from: DataType,
to: DataType,
input: &'de [u8],
) -> Result<T, DataConversionError>
where
T: Deserialize<'de>,
{
let decode = JsonDecodeSession::borrowing_value(self.budget.structured_mut());
JsonDecoder::new(decode)
.decode_utf8(input)
.map_err(|error| map_json_decode_error(from, to, error))
}
#[cfg(feature = "json")]
pub(crate) fn decode_json_seed<'de, S>(
&mut self,
from: DataType,
to: DataType,
seed: S,
input: &'de [u8],
) -> Result<S::Value, DataConversionError>
where
S: DeserializeSeed<'de>,
{
let decode = JsonDecodeSession::borrowing_value(self.budget.structured_mut());
JsonDecoder::new(decode)
.decode_seed_utf8(seed, input)
.map_err(|error| map_json_decode_error(from, to, error))
}
#[cfg(feature = "json")]
pub(crate) fn account_json_value(
&mut self,
from: DataType,
to: DataType,
value: &Value,
) -> Result<(), DataConversionError> {
let mut transaction = self.budget.structured_transaction();
JsonTreeReader::new(&mut transaction)
.account(value)
.map_err(|error| DataConversionError::measured_limit(from, to, error))?;
transaction
.commit()
.map_err(|error| DataConversionError::measured_limit(from, to, error))
}
pub(crate) fn structured_transaction(&mut self) -> JsonValueTransaction<'_, ConversionResource, u64> {
self.budget.structured_transaction()
}
#[cfg(feature = "json")]
pub(crate) fn encode_json<T>(
&mut self,
from: DataType,
to: DataType,
value: &T,
) -> Result<Vec<u8>, DataConversionError>
where
T: Serialize + ?Sized,
{
let (output, structured) = self.budget.split_json_mut();
let encode = JsonEncodeSession::borrowing_output(output, structured);
JsonEncoder::new(encode)
.to_vec(value)
.map_err(|error| map_json_encode_error(from, to, error))
}
#[inline]
fn admit_scalar_source(&mut self, from: DataType, to: DataType, amount: usize) -> Result<(), DataConversionError> {
self.admit_scalar_source_bytes_usize(amount)
.map_err(|error| DataConversionError::measured_limit(from, to, error))
}
#[inline]
pub fn admit_scalar_string_source<'session, 'source>(
&'session mut self,
source: &'source str,
) -> Result<AdmittedScalarSource<'session, 'a, 'source>, DataConversionError>
where
'a: 'source,
{
self.admit_scalar_source(DataType::String, DataType::String, source.len())?;
Ok(AdmittedScalarSource::new(self, source))
}
#[inline]
pub(crate) fn admit_output_bytes(
&mut self,
from: DataType,
to: DataType,
amount: usize,
) -> Result<(), DataConversionError> {
self.budget
.consume_output_bytes_usize(amount)
.map_err(|error| DataConversionError::measured_limit(from, to, error))
}
#[inline]
pub(crate) fn write_string<F>(
&mut self,
from: DataType,
to: DataType,
render: F,
) -> Result<String, DataConversionError>
where
F: FnOnce(&mut ConversionStringWriter<'_>) -> Result<(), DataConversionError>,
{
self.budget
.try_write_string(|writer| render(&mut ConversionStringWriter::new(writer, from, to)))
.map_err(|error| map_string_write_error(from, to, error))
}
#[inline]
pub(crate) fn consume_item(&mut self) -> Result<(), BudgetError<ConversionResource, u64>> {
self.budget.consume_item()
}
#[inline]
pub(crate) fn admit_scalar_source_bytes_usize(
&mut self,
amount: usize,
) -> Result<(), MeasuredBudgetError<ConversionResource, u64>> {
self.check_collection_source_bytes_usize(amount)?;
self.consume_input_bytes_usize(amount)
}
#[inline]
pub(crate) fn consume_input_bytes_usize(
&mut self,
amount: usize,
) -> Result<(), MeasuredBudgetError<ConversionResource, u64>> {
self.budget.consume_input_bytes_usize(amount)
}
#[inline]
pub(crate) fn check_collection_source_bytes_usize(
&self,
actual: usize,
) -> Result<(), MeasuredBudgetError<ConversionResource, u64>> {
let limit = ResourceLimit::new(
ConversionResource::CollectionSourceBytes,
self.limits.collection().max_source_bytes(),
);
let actual = u64::try_from_usize(actual)
.map_err(|source| MeasuredBudgetError::quantity(ConversionResource::CollectionSourceBytes, source))?;
limit.check(actual).map_err(MeasuredBudgetError::from)
}
#[inline(always)]
#[must_use]
pub const fn items_remaining(&self) -> u64 {
self.budget.remaining_items()
}
#[inline(always)]
#[must_use]
pub const fn items_limit(&self) -> u64 {
self.limits.operation().max_items()
}
#[inline(always)]
#[must_use]
pub const fn input_bytes_limit(&self) -> u64 {
self.limits.operation().max_input_bytes()
}
#[inline(always)]
#[must_use]
pub const fn output_bytes_limit(&self) -> u64 {
self.limits.operation().max_output_bytes()
}
#[inline(always)]
#[must_use]
pub const fn structured_nodes_limit(&self) -> u64 {
self.limits.operation().max_structured_nodes()
}
#[inline(always)]
#[must_use]
pub const fn structured_payload_bytes_limit(&self) -> u64 {
self.limits.operation().max_structured_payload_bytes()
}
#[inline(always)]
pub(crate) fn remaining_items(&self) -> u64 {
self.items_remaining()
}
#[inline(always)]
#[must_use]
pub fn items_used(&self) -> u64 {
self.budget.items_used()
}
#[inline(always)]
#[must_use]
pub fn input_bytes_used(&self) -> u64 {
self.budget.input_bytes_used()
}
#[inline(always)]
#[must_use]
pub fn input_bytes_remaining(&self) -> u64 {
self.budget.input_bytes_remaining()
}
#[inline(always)]
#[must_use]
pub fn output_bytes_used(&self) -> u64 {
self.budget.output_bytes_used()
}
#[inline(always)]
#[must_use]
pub fn output_bytes_remaining(&self) -> u64 {
self.budget.output_bytes_remaining()
}
#[inline(always)]
#[must_use]
pub fn structured_nodes_used(&self) -> u64 {
self.budget.structured_nodes_used()
}
#[inline(always)]
#[must_use]
pub fn structured_payload_bytes_used(&self) -> u64 {
self.budget.structured_payload_bytes_used()
}
}
fn map_string_write_error(
from: DataType,
to: DataType,
error: BudgetedStringError<ConversionResource, DataConversionError>,
) -> DataConversionError {
match error {
BudgetedStringError::Budget(error) => DataConversionError::limit_exceeded(from, to, error.into()),
BudgetedStringError::Quantity { resource, source } => DataConversionError::quantity(from, to, resource, source),
BudgetedStringError::Render(error) => error,
BudgetedStringError::InvalidUtf8(_)
| BudgetedStringError::LengthOverflow
| BudgetedStringError::Allocation(_) => {
DataConversionError::invalid(from, to, super::error::InvalidValueReason::OutOfRange)
}
}
}
#[cfg(feature = "json")]
fn map_json_decode_error(
from: DataType,
to: DataType,
error: JsonDecodeError<ConversionResource, u64>,
) -> DataConversionError {
match error.budget_error().cloned() {
Some(error) => DataConversionError::measured_limit(from, to, error),
None => DataConversionError::invalid(
from,
to,
super::error::InvalidValueReason::Deserialization {
format: super::error::DataFormat::Json,
},
),
}
}
#[cfg(feature = "json")]
fn map_json_encode_error(
from: DataType,
to: DataType,
error: JsonEncodeError<ConversionResource, u64>,
) -> DataConversionError {
if let Some(error) = error.into_budget_error() {
DataConversionError::measured_limit(from, to, error)
} else {
DataConversionError::invalid(
from,
to,
super::error::InvalidValueReason::Serialization {
format: super::error::DataFormat::Json,
},
)
}
}