qubit-value 0.13.0

Type-safe containers for single, multi-valued, and named runtime values
Documentation
// =============================================================================
//    Copyright (c) 2025 - 2026 Haixing Hu.
//
//    SPDX-License-Identifier: Apache-2.0
//
//    Licensed under the Apache License, Version 2.0.
// =============================================================================

//! Errors produced while constructing a V1 wire DTO.

#[cfg(feature = "json")]
use std::io::Error as IoError;

#[cfg(feature = "json")]
use qubit_budget::BudgetError;
#[cfg(feature = "json")]
use qubit_budget::MeasuredBudgetError;
#[cfg(feature = "json")]
use qubit_budget::QuantityConversionError;
#[cfg(feature = "json")]
use qubit_budget::json::JsonResource;
use qubit_datatype::DataType;
#[cfg(feature = "json")]
use qubit_json::decode::JsonSyntaxError;
#[cfg(feature = "json")]
use qubit_json::encode::JsonEncodeError;
#[cfg(feature = "json")]
use qubit_json::encode::JsonEncodeErrorSource;
#[cfg(feature = "json")]
use qubit_json::encode::JsonSerializationError;
use thiserror::Error;

/// A runtime value cannot be represented by the JSON V1 wire contract.
///
/// # Examples
///
/// ```
/// use qubit_value::Value;
/// use qubit_value::ValueWireEncodeError;
/// use qubit_value::ValueWireV1;
///
/// let error = ValueWireV1::try_from(Value::Float64(f64::NAN)).unwrap_err();
/// assert!(matches!(error, ValueWireEncodeError::NonFiniteFloat { .. }));
/// ```
#[non_exhaustive]
#[must_use]
#[derive(Debug, Error)]
pub enum ValueWireEncodeError {
    /// A JSON number cannot be restored with the V1 number model without
    /// changing its `serde_json::Number` identity.
    #[cfg(feature = "json")]
    #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
    #[error("V1 JSON wire cannot preserve this JSON number representation")]
    JsonNumberNotRoundTrippable,
    /// A JSON V1 float must be finite.
    #[error("V1 JSON wire cannot represent a non-finite {data_type} value")]
    NonFiniteFloat {
        /// Runtime data type of the rejected value.
        data_type: DataType,
    },
    /// A V1 decimal exponent must stay within the bounded wire range.
    #[error("V1 JSON wire cannot represent decimal scale {scale}; maximum absolute scale is {maximum_absolute_scale}")]
    BigDecimalScaleTooLarge {
        /// Rejected decimal exponent.
        scale: i64,
        /// Inclusive exponent magnitude limit for V1.
        maximum_absolute_scale: i64,
    },
    /// A UTC instant year must be within the four-digit V1 wire range.
    #[error("V1 JSON wire cannot represent UTC instant year {year}; supported years are 0 through 9999")]
    InstantYearOutOfRange {
        /// UTC calendar year rejected by the V1 wire contract.
        year: i32,
    },
    /// The JSON output exceeded one configured resource budget.
    #[cfg(feature = "json")]
    #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
    #[error("V1 JSON wire resource budget exceeded: {0}")]
    Budget(
        /// Budget violation reported by the bounded JSON encoder.
        #[source]
        BudgetError<JsonResource, usize>,
    ),
    /// A native JSON measurement could not be represented by the budget
    /// quantity type.
    #[cfg(feature = "json")]
    #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
    #[error("V1 JSON wire resource quantity conversion failed for {resource:?}: {source}")]
    Quantity {
        /// Resource whose measurement failed.
        resource: JsonResource,
        /// Native measurement conversion failure.
        #[source]
        source: QuantityConversionError,
    },
    /// The encoded value contains invalid JSON syntax.
    #[cfg(feature = "json")]
    #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
    #[error("invalid V1 JSON wire syntax: {0}")]
    Syntax(
        /// Syntax error found in an embedded raw JSON payload.
        #[source]
        JsonSyntaxError,
    ),
    /// Strict JSON serialization rejected the value during bounded encoding.
    #[cfg(feature = "json")]
    #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
    #[error("failed to encode V1 JSON wire value: {0}")]
    Json(
        /// Stable, privacy-safe JSON serialization failure.
        #[source]
        JsonSerializationError,
    ),
    /// The destination writer rejected bounded JSON output.
    #[cfg(feature = "json")]
    #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
    #[error("failed to write V1 JSON wire value: {0}")]
    Io(
        /// Destination-writer failure.
        #[source]
        IoError,
    ),
}

#[cfg(feature = "json")]
impl From<JsonEncodeError<JsonResource, usize>> for ValueWireEncodeError {
    /// Converts bounded JSON encoder failures into wire encoding errors.
    ///
    /// Resource-budget failures retain their budget details, invalid raw JSON
    /// retains its syntax error, and serialization or writer failures retain
    /// their corresponding source errors.
    ///
    /// # Parameters
    ///
    /// * `error` - Failure reported by the bounded JSON encoder.
    ///
    /// # Returns
    ///
    /// The wire encoding error corresponding to the encoder failure.
    #[inline]
    fn from(error: JsonEncodeError<JsonResource>) -> Self {
        match error.into_source() {
            JsonEncodeErrorSource::Budget(source) => Self::from(source),
            JsonEncodeErrorSource::InvalidRawJson(source) => Self::Syntax(source),
            JsonEncodeErrorSource::Serialize(source) => Self::Json(source),
            JsonEncodeErrorSource::Write(source) => Self::Io(source),
        }
    }
}

#[cfg(feature = "json")]
impl From<MeasuredBudgetError<JsonResource, usize>> for ValueWireEncodeError {
    /// Converts a measured budget failure into its corresponding wire error.
    ///
    /// Budget violations retain their budget details, while quantity
    /// conversion failures retain the affected resource and source error.
    ///
    /// # Parameters
    ///
    /// * `error` - Measured failure reported by the budget layer.
    ///
    /// # Returns
    ///
    /// The wire encoding error corresponding to the measured failure.
    #[inline]
    fn from(error: MeasuredBudgetError<JsonResource, usize>) -> Self {
        match error {
            MeasuredBudgetError::Budget(error) => Self::Budget(error),
            MeasuredBudgetError::Quantity { resource, source } => Self::Quantity { resource, source },
        }
    }
}