Skip to main content

qubit_json/decode/
json_decode_error_kind.rs

1// =============================================================================
2//    Copyright (c) 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! Defines stable categories for JSON decoding failures.
9
10use std::fmt;
11use std::str::FromStr;
12
13/// Represents the coarse category of a JSON decoding failure.
14///
15/// This exhaustive enum is the stable branching contract shared by strict and
16/// normalizing decoders.
17///
18/// # Examples
19///
20/// ```
21/// use qubit_json::decode::JsonDecodeErrorKind;
22///
23/// let kind = "invalid_json".parse::<JsonDecodeErrorKind>()?;
24/// assert_eq!(kind, JsonDecodeErrorKind::InvalidJson);
25/// # Ok::<(), &'static str>(())
26/// ```
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28pub enum JsonDecodeErrorKind {
29    /// Reading bytes from the input failed.
30    Read,
31    /// A configured resource budget rejected a measurement.
32    Budget,
33    /// Normalization produced no JSON document from the supplied input.
34    EmptyInput,
35    /// Raw byte input was not valid UTF-8.
36    InvalidUtf8,
37    /// Input was not one valid JSON document under the numeric contract.
38    InvalidJson,
39    /// A valid document had an unexpected top-level kind.
40    UnexpectedTopLevel,
41    /// A valid admitted document could not deserialize into the target type.
42    Deserialize,
43}
44
45impl fmt::Display for JsonDecodeErrorKind {
46    /// Writes the stable snake-case category name.
47    ///
48    /// # Parameters
49    ///
50    /// * `formatter` - Destination for the category name.
51    ///
52    /// # Returns
53    ///
54    /// `Ok(())` when the category name is written successfully.
55    ///
56    /// # Errors
57    ///
58    /// Returns the formatter's error if the destination rejects the write.
59    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
60        formatter.write_str(match self {
61            Self::Read => "read",
62            Self::Budget => "budget",
63            Self::EmptyInput => "empty_input",
64            Self::InvalidUtf8 => "invalid_utf8",
65            Self::InvalidJson => "invalid_json",
66            Self::UnexpectedTopLevel => "unexpected_top_level",
67            Self::Deserialize => "deserialize",
68        })
69    }
70}
71
72impl FromStr for JsonDecodeErrorKind {
73    /// Uses a static message to report unsupported category names.
74    type Err = &'static str;
75
76    /// Parses a stable category name without ASCII case sensitivity.
77    ///
78    /// # Parameters
79    ///
80    /// * `value` - Category name to parse.
81    ///
82    /// # Returns
83    ///
84    /// The matching JSON decoding error category.
85    ///
86    /// # Errors
87    ///
88    /// Returns `"unknown JsonDecodeErrorKind"` when `value` does not match a
89    /// supported category name.
90    fn from_str(value: &str) -> Result<Self, Self::Err> {
91        if value.eq_ignore_ascii_case("read") {
92            Ok(Self::Read)
93        } else if value.eq_ignore_ascii_case("budget") {
94            Ok(Self::Budget)
95        } else if value.eq_ignore_ascii_case("empty_input") {
96            Ok(Self::EmptyInput)
97        } else if value.eq_ignore_ascii_case("invalid_utf8") {
98            Ok(Self::InvalidUtf8)
99        } else if value.eq_ignore_ascii_case("invalid_json") {
100            Ok(Self::InvalidJson)
101        } else if value.eq_ignore_ascii_case("unexpected_top_level") {
102            Ok(Self::UnexpectedTopLevel)
103        } else if value.eq_ignore_ascii_case("deserialize") {
104            Ok(Self::Deserialize)
105        } else {
106            Err("unknown JsonDecodeErrorKind")
107        }
108    }
109}