use std::cell::RefCell;
use std::fmt::Debug;
use std::io::Write;
use qubit_budget::ResourceQuantity;
use qubit_budget::json::JsonEncodeAttempt;
use qubit_budget::json::JsonEncodeLimits;
use qubit_budget::json::JsonEncodeSession;
use qubit_budget::json::JsonResource;
use qubit_io::ByteOutput;
use serde_json::Value;
use super::JcsEncodeError;
use super::JsonSerializationError;
use super::JsonSerializationErrorKind;
use super::internal::JcsRenderer;
use super::internal::JcsRendererError;
use super::output::JsonOutputAccounting;
use super::output::JsonOutputBuffer;
use super::output::JsonOutputWriter;
use crate::value::traverse::JsonTreeReader;
#[must_use]
pub struct JcsEncoder<'budget, R = JsonResource, Q = usize>
where
Q: ResourceQuantity,
{
session: JsonEncodeSession<'budget, R, Q>,
max_working_bytes: usize,
}
impl<R, Q> JcsEncoder<'static, R, Q>
where
R: Clone + Debug,
Q: ResourceQuantity,
{
pub fn with_limits(limits: JsonEncodeLimits<R, Q>, max_working_bytes: usize) -> Self {
Self::new(JsonEncodeSession::from_limits(limits), max_working_bytes)
}
}
impl JcsEncoder<'static, JsonResource, usize> {
pub fn unlimited() -> Self {
Self::with_limits(JsonEncodeLimits::new(), usize::MAX)
}
}
impl<'budget, R, Q> JcsEncoder<'budget, R, Q>
where
R: Clone + Debug,
Q: ResourceQuantity,
{
pub fn new(session: JsonEncodeSession<'budget, R, Q>, max_working_bytes: usize) -> Self {
Self {
session,
max_working_bytes,
}
}
#[must_use]
#[inline]
pub const fn max_working_bytes(&self) -> usize {
self.max_working_bytes
}
#[must_use]
#[inline]
pub const fn session(&self) -> &JsonEncodeSession<'budget, R, Q> {
&self.session
}
#[must_use]
#[inline]
pub const fn session_mut(&mut self) -> &mut JsonEncodeSession<'budget, R, Q> {
&mut self.session
}
#[must_use]
pub fn into_session(self) -> JsonEncodeSession<'budget, R, Q> {
self.session
}
pub fn to_vec(&mut self, value: &Value) -> Result<Vec<u8>, JcsEncodeError<R, Q>> {
let mut attempt = self.session.begin_value();
let bytes = Self::serialize_buffer(value, &mut attempt, self.max_working_bytes)?;
attempt
.try_consume_output_bytes(bytes.len())
.map_err(JcsEncodeError::budget)?;
attempt.commit().map_err(JcsEncodeError::budget)?;
Ok(bytes)
}
pub fn to_string(&mut self, value: &Value) -> Result<String, JcsEncodeError<R, Q>> {
let bytes = self.to_vec(value)?;
Ok(String::from_utf8(bytes).expect("JCS output must be UTF-8"))
}
pub fn write_buffered<O: ByteOutput + ?Sized>(
&mut self,
output: &mut O,
value: &Value,
) -> Result<(), JcsEncodeError<R, Q>> {
let mut attempt = self.session.begin_value();
let bytes = Self::serialize_buffer(value, &mut attempt, self.max_working_bytes)?;
attempt
.check_output_bytes(bytes.len())
.map_err(JcsEncodeError::budget)?;
Self::write_buffer(output, &bytes, &mut attempt)?;
attempt.commit().map_err(JcsEncodeError::budget)
}
pub fn write_incremental<O: ByteOutput + ?Sized>(
&mut self,
output: &mut O,
value: &Value,
) -> Result<(), JcsEncodeError<R, Q>> {
let mut attempt = self.session.begin_value();
JsonTreeReader::new(attempt.value_transaction_mut())
.account(value)
.map_err(JcsEncodeError::budget)?;
let result = {
let (output_budget, _) = attempt.split_mut();
let accounting = RefCell::new(JsonOutputAccounting::new(output_budget));
let mut writer = JsonOutputWriter::new(output, &accounting);
match JcsRenderer::render(value, &mut writer, self.max_working_bytes) {
Ok(()) => {
let result = Write::flush(&mut writer).map_err(serde_json::Error::io);
writer
.into_result(result)
.map_err(JcsEncodeError::from_json_encode)
}
Err(JcsRendererError::WorkingMemory(error)) => {
Err(JcsEncodeError::working_memory(error))
}
Err(JcsRendererError::Number(source)) => Err(JcsEncodeError::serialization(source)),
Err(JcsRendererError::Allocation) => Err(JcsEncodeError::serialization(
JsonSerializationError::new(JsonSerializationErrorKind::CustomSerialization),
)),
Err(JcsRendererError::Serialize(error)) => writer
.into_result(Err(error))
.map_err(JcsEncodeError::from_json_encode),
}
};
result?;
attempt.commit().map_err(JcsEncodeError::budget)
}
fn serialize_buffer(
value: &Value,
attempt: &mut JsonEncodeAttempt<'_, R, Q>,
max_working_bytes: usize,
) -> Result<Vec<u8>, JcsEncodeError<R, Q>> {
JsonTreeReader::new(attempt.value_transaction_mut())
.account(value)
.map_err(JcsEncodeError::budget)?;
let (output_budget, _) = attempt.split_mut();
let accounting = RefCell::new(JsonOutputAccounting::new(output_budget));
let mut output = JsonOutputBuffer::new(&accounting);
match JcsRenderer::render(value, &mut output, max_working_bytes) {
Ok(()) => output
.into_result(Ok(()))
.map_err(JcsEncodeError::from_json_encode),
Err(JcsRendererError::WorkingMemory(error)) => {
Err(JcsEncodeError::working_memory(error))
}
Err(JcsRendererError::Number(source)) => Err(JcsEncodeError::serialization(source)),
Err(JcsRendererError::Allocation) => Err(JcsEncodeError::serialization(
JsonSerializationError::new(JsonSerializationErrorKind::CustomSerialization),
)),
Err(JcsRendererError::Serialize(error)) => output
.into_result(Err(error))
.map_err(JcsEncodeError::from_json_encode),
}
}
fn write_buffer<O: ByteOutput + ?Sized>(
output: &mut O,
bytes: &[u8],
attempt: &mut JsonEncodeAttempt<'_, R, Q>,
) -> Result<(), JcsEncodeError<R, Q>> {
let (output_budget, _) = attempt.split_mut();
let accounting = RefCell::new(JsonOutputAccounting::new(output_budget));
let mut writer = JsonOutputWriter::new(output, &accounting);
let result = writer.write_all(bytes).map_err(serde_json::Error::io);
writer
.into_result(result)
.map_err(JcsEncodeError::from_json_encode)
}
}