use std::error::Error;
use std::fmt;
use std::sync::Arc;
use qubit_budget::MeasuredBudgetError;
use qubit_budget::ResourceQuantity;
use qubit_budget::json::JsonResource;
use serde_json::Error as JsonError;
use super::DiagnosticPolicy;
use super::JsonDecodeErrorKind;
use super::JsonDecodeErrorSource;
use super::JsonDecodeStage;
use super::JsonRootKind;
use super::JsonSyntaxError;
use crate::lexical::JsonLexicalFailure;
#[must_use]
#[derive(Clone)]
pub struct JsonDecodeError<R = JsonResource, Q = usize>
where
Q: Copy + fmt::Debug,
{
diagnostic_policy: DiagnosticPolicy,
failure: JsonDecodeErrorSource<R, Q>,
}
impl<R, Q> JsonDecodeError<R, Q>
where
Q: Copy + fmt::Debug,
{
#[must_use = "return or inspect the constructed decoding error"]
pub(crate) const fn budget(
source: MeasuredBudgetError<R, Q>,
stage: JsonDecodeStage,
raw_input_bytes: usize,
normalized_input_bytes: Option<usize>,
diagnostic_policy: DiagnosticPolicy,
) -> Self {
Self {
diagnostic_policy,
failure: JsonDecodeErrorSource::Budget {
stage,
raw_input_bytes,
normalized_input_bytes,
source,
},
}
}
#[must_use = "return or inspect the constructed decoding error"]
pub(crate) const fn empty_input(
stage: JsonDecodeStage,
raw_input_bytes: usize,
normalized_input_bytes: Option<usize>,
diagnostic_policy: DiagnosticPolicy,
) -> Self {
Self {
diagnostic_policy,
failure: JsonDecodeErrorSource::EmptyInput {
stage,
raw_input_bytes,
normalized_input_bytes,
},
}
}
#[must_use = "return or inspect the constructed decoding error"]
pub(crate) fn invalid_utf8(
source: std::str::Utf8Error,
raw_input_bytes: usize,
diagnostic_policy: DiagnosticPolicy,
) -> Self {
let valid_up_to = source.valid_up_to();
let error_len = source.error_len();
let source = (diagnostic_policy == DiagnosticPolicy::Detailed).then_some(source);
Self {
diagnostic_policy,
failure: JsonDecodeErrorSource::InvalidUtf8 {
raw_input_bytes,
valid_up_to,
error_len,
source,
},
}
}
#[must_use = "return or inspect the constructed decoding error"]
pub(crate) fn invalid_json(
syntax_source: JsonLexicalFailure,
detailed_source: Option<Arc<dyn Error + Send + Sync>>,
raw_input_bytes: usize,
normalized_input_bytes: Option<usize>,
diagnostic_policy: DiagnosticPolicy,
) -> Self {
Self {
diagnostic_policy,
failure: JsonDecodeErrorSource::InvalidJson {
raw_input_bytes,
normalized_input_bytes,
syntax: JsonSyntaxError::from_lexical(syntax_source),
source: detailed_source,
},
}
}
#[must_use = "return or inspect the constructed decoding error"]
pub(crate) const fn unexpected_top_level(
expected: JsonRootKind,
actual: JsonRootKind,
raw_input_bytes: usize,
normalized_input_bytes: Option<usize>,
diagnostic_policy: DiagnosticPolicy,
) -> Self {
Self {
diagnostic_policy,
failure: JsonDecodeErrorSource::UnexpectedTopLevel {
raw_input_bytes,
normalized_input_bytes,
expected,
actual,
},
}
}
#[must_use = "return or inspect the constructed decoding error"]
pub(crate) fn deserialize(
source: JsonError,
raw_input_bytes: usize,
normalized_input_bytes: Option<usize>,
diagnostic_policy: DiagnosticPolicy,
) -> Self {
let line = source.line();
let column = source.column();
let source =
(diagnostic_policy == DiagnosticPolicy::Detailed).then(|| Arc::new(source) as Arc<dyn Error + Send + Sync>);
Self {
diagnostic_policy,
failure: JsonDecodeErrorSource::Deserialize {
raw_input_bytes,
normalized_input_bytes,
line,
column,
source,
},
}
}
#[must_use]
#[inline(always)]
pub const fn kind(&self) -> JsonDecodeErrorKind {
match self.failure {
JsonDecodeErrorSource::Budget { .. } => JsonDecodeErrorKind::Budget,
JsonDecodeErrorSource::EmptyInput { .. } => JsonDecodeErrorKind::EmptyInput,
JsonDecodeErrorSource::InvalidUtf8 { .. } => JsonDecodeErrorKind::InvalidUtf8,
JsonDecodeErrorSource::InvalidJson { .. } => JsonDecodeErrorKind::InvalidJson,
JsonDecodeErrorSource::UnexpectedTopLevel { .. } => JsonDecodeErrorKind::UnexpectedTopLevel,
JsonDecodeErrorSource::Deserialize { .. } => JsonDecodeErrorKind::Deserialize,
}
}
#[must_use]
#[inline(always)]
pub const fn stage(&self) -> JsonDecodeStage {
match self.failure {
JsonDecodeErrorSource::Budget { stage, .. } | JsonDecodeErrorSource::EmptyInput { stage, .. } => stage,
JsonDecodeErrorSource::InvalidUtf8 { .. } => JsonDecodeStage::DecodeText,
JsonDecodeErrorSource::InvalidJson { .. } => JsonDecodeStage::Parse,
JsonDecodeErrorSource::UnexpectedTopLevel { .. } => JsonDecodeStage::TopLevelCheck,
JsonDecodeErrorSource::Deserialize { .. } => JsonDecodeStage::Deserialize,
}
}
#[must_use]
#[inline(always)]
pub const fn diagnostic_policy(&self) -> DiagnosticPolicy {
self.diagnostic_policy
}
#[must_use]
#[inline(always)]
pub const fn raw_input_bytes(&self) -> usize {
match self.failure {
JsonDecodeErrorSource::Budget { raw_input_bytes, .. }
| JsonDecodeErrorSource::EmptyInput { raw_input_bytes, .. }
| JsonDecodeErrorSource::InvalidUtf8 { raw_input_bytes, .. }
| JsonDecodeErrorSource::InvalidJson { raw_input_bytes, .. }
| JsonDecodeErrorSource::UnexpectedTopLevel { raw_input_bytes, .. }
| JsonDecodeErrorSource::Deserialize { raw_input_bytes, .. } => raw_input_bytes,
}
}
#[must_use]
#[inline(always)]
pub const fn normalized_input_bytes(&self) -> Option<usize> {
match self.failure {
JsonDecodeErrorSource::Budget {
normalized_input_bytes, ..
}
| JsonDecodeErrorSource::EmptyInput {
normalized_input_bytes, ..
}
| JsonDecodeErrorSource::InvalidJson {
normalized_input_bytes, ..
}
| JsonDecodeErrorSource::UnexpectedTopLevel {
normalized_input_bytes, ..
}
| JsonDecodeErrorSource::Deserialize {
normalized_input_bytes, ..
} => normalized_input_bytes,
JsonDecodeErrorSource::InvalidUtf8 { .. } => None,
}
}
#[must_use]
#[inline(always)]
pub const fn line(&self) -> Option<usize> {
match &self.failure {
JsonDecodeErrorSource::InvalidJson { syntax, .. } => Some(syntax.line()),
JsonDecodeErrorSource::Deserialize { line, .. } if *line > 0 => Some(*line),
_ => None,
}
}
#[must_use]
#[inline(always)]
pub const fn column(&self) -> Option<usize> {
match &self.failure {
JsonDecodeErrorSource::InvalidJson { syntax, .. } => Some(syntax.column()),
JsonDecodeErrorSource::Deserialize { column, .. } if *column > 0 => Some(*column),
_ => None,
}
}
#[must_use]
#[inline(always)]
pub const fn syntax_error(&self) -> Option<&JsonSyntaxError> {
match &self.failure {
JsonDecodeErrorSource::InvalidJson { syntax, .. } => Some(syntax),
_ => None,
}
}
#[must_use]
#[inline(always)]
pub const fn budget_error(&self) -> Option<&MeasuredBudgetError<R, Q>> {
match &self.failure {
JsonDecodeErrorSource::Budget { source, .. } => Some(source),
_ => None,
}
}
#[must_use]
#[inline(always)]
pub const fn utf8_valid_up_to(&self) -> Option<usize> {
match self.failure {
JsonDecodeErrorSource::InvalidUtf8 { valid_up_to, .. } => Some(valid_up_to),
_ => None,
}
}
#[must_use]
#[inline(always)]
pub const fn utf8_error_len(&self) -> Option<usize> {
match self.failure {
JsonDecodeErrorSource::InvalidUtf8 { error_len, .. } => error_len,
_ => None,
}
}
#[must_use]
#[inline(always)]
pub const fn expected_top_level(&self) -> Option<JsonRootKind> {
match self.failure {
JsonDecodeErrorSource::UnexpectedTopLevel { expected, .. } => Some(expected),
_ => None,
}
}
#[must_use]
#[inline(always)]
pub const fn actual_top_level(&self) -> Option<JsonRootKind> {
match self.failure {
JsonDecodeErrorSource::UnexpectedTopLevel { actual, .. } => Some(actual),
_ => None,
}
}
#[inline(always)]
pub fn into_source(self) -> JsonDecodeErrorSource<R, Q> {
self.failure
}
}
impl<R, Q> fmt::Debug for JsonDecodeError<R, Q>
where
R: fmt::Debug,
Q: Copy + fmt::Debug,
{
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("JsonDecodeError")
.field("diagnostic_policy", &self.diagnostic_policy)
.field("failure", &self.failure)
.finish()
}
}
impl<R, Q> fmt::Display for JsonDecodeError<R, Q>
where
R: fmt::Debug,
Q: ResourceQuantity,
{
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.failure {
JsonDecodeErrorSource::Budget { source, .. } => {
write!(formatter, "JSON resource budget rejected input: {source}")
}
JsonDecodeErrorSource::EmptyInput { .. } => formatter.write_str("JSON input is empty after normalization"),
JsonDecodeErrorSource::InvalidUtf8 { source, .. } => match source {
Some(source) => write!(formatter, "Failed to decode JSON input as UTF-8: {source}"),
None => formatter.write_str("Failed to decode JSON input as UTF-8"),
},
JsonDecodeErrorSource::InvalidJson { syntax, source, .. } => match source {
Some(source) => {
write!(formatter, "Failed to parse JSON: {source}")
}
None => write!(formatter, "Failed to parse JSON: {syntax}"),
},
JsonDecodeErrorSource::UnexpectedTopLevel { expected, actual, .. } => {
write!(
formatter,
"Unexpected JSON top-level type: expected {expected}, got {actual}"
)
}
JsonDecodeErrorSource::Deserialize {
normalized_input_bytes,
line,
column,
source,
..
} => match source {
Some(source) => write!(formatter, "Failed to deserialize JSON value: {source}"),
None if normalized_input_bytes.is_some() => write!(
formatter,
"Failed to deserialize JSON value at normalized line {line} column {column}"
),
None => write!(
formatter,
"Failed to deserialize JSON value at line {line} column {column}"
),
},
}
}
}
impl<R, Q> Error for JsonDecodeError<R, Q>
where
R: fmt::Debug + 'static,
Q: ResourceQuantity + 'static,
{
fn source(&self) -> Option<&(dyn Error + 'static)> {
match &self.failure {
JsonDecodeErrorSource::Budget { source, .. } => Some(source),
JsonDecodeErrorSource::InvalidUtf8 {
source: Some(source), ..
} => Some(source),
JsonDecodeErrorSource::InvalidJson {
source: Some(source), ..
} => Some(source.as_ref()),
JsonDecodeErrorSource::Deserialize {
source: Some(source), ..
} => Some(source.as_ref()),
_ => None,
}
}
}