qubit_json/decode/json_root_kind.rs
1// =============================================================================
2// Copyright (c) 2025 - 2026 Haixing Hu.
3//
4// SPDX-License-Identifier: Apache-2.0
5//
6// Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! Defines the coarse top-level JSON kinds used by constrained decode methods.
9
10use std::fmt;
11use std::str::FromStr;
12
13use serde_json::Value;
14
15/// Represents the top-level kind of a parsed JSON value.
16///
17/// The decoder uses this type to report whether the parsed value is an object,
18/// an array, or any other scalar-like JSON value.
19///
20/// # Examples
21///
22/// ```
23/// use qubit_json::decode::JsonRootKind;
24///
25/// let top_level_kind = JsonRootKind::Other;
26/// assert_eq!(top_level_kind.to_string(), "other");
27/// ```
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
29pub enum JsonRootKind {
30 /// Indicates that the parsed top-level value is a JSON object.
31 Object,
32 /// Indicates that the parsed top-level value is a JSON array.
33 Array,
34 /// Indicates that the parsed top-level value is neither an object nor an
35 /// array.
36 Other,
37}
38
39impl JsonRootKind {
40 /// Classifies the top-level kind of `value`.
41 ///
42 /// This helper is used internally by constrained decode methods and may
43 /// also be useful to callers inspecting decoded [`Value`] instances.
44 ///
45 /// # Parameters
46 ///
47 /// * `value` - JSON value to classify.
48 ///
49 /// # Returns
50 ///
51 /// [`Self::Object`] for objects, [`Self::Array`] for arrays, and
52 /// [`Self::Other`] for scalar-like values.
53 #[must_use]
54 #[inline]
55 pub fn of(value: &Value) -> Self {
56 match value {
57 Value::Object(_) => Self::Object,
58 Value::Array(_) => Self::Array,
59 _ => Self::Other,
60 }
61 }
62
63 /// Provisionally classifies normalized JSON text by its first JSON token.
64 ///
65 /// This helper does not validate JSON syntax. The decoder uses the result
66 /// only as a pre-parse top-level-kind hint.
67 ///
68 /// # Parameters
69 ///
70 /// * `value` - Normalized JSON text to inspect.
71 ///
72 /// # Returns
73 ///
74 /// [`Self::Object`] when the first token is `{`, [`Self::Array`] when it is
75 /// `[`, and [`Self::Other`] otherwise.
76 pub(crate) fn of_normalized_json(value: &str) -> Self {
77 match value
78 .bytes()
79 .find(|byte| !matches!(*byte, b' ' | b'\n' | b'\r' | b'\t'))
80 {
81 Some(b'{') => Self::Object,
82 Some(b'[') => Self::Array,
83 _ => Self::Other,
84 }
85 }
86}
87
88impl From<&Value> for JsonRootKind {
89 /// Classifies a borrowed dynamic JSON value.
90 ///
91 /// # Parameters
92 ///
93 /// * `value` - JSON value to classify.
94 ///
95 /// # Returns
96 ///
97 /// The value's coarse top-level kind.
98 #[inline(always)]
99 fn from(value: &Value) -> Self {
100 Self::of(value)
101 }
102}
103
104impl fmt::Display for JsonRootKind {
105 /// Writes the stable lowercase name of this top-level kind.
106 ///
107 /// # Parameters
108 ///
109 /// * `f` - Destination formatter.
110 ///
111 /// # Returns
112 ///
113 /// `Ok(())` when the kind name is written successfully.
114 ///
115 /// # Errors
116 ///
117 /// Returns a formatting error when the destination rejects the write.
118 #[inline]
119 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120 let name = match self {
121 Self::Object => "object",
122 Self::Array => "array",
123 Self::Other => "other",
124 };
125 f.write_str(name)
126 }
127}
128
129impl FromStr for JsonRootKind {
130 type Err = &'static str;
131
132 /// Parses a top-level kind name without ASCII case sensitivity.
133 ///
134 /// # Parameters
135 ///
136 /// * `value` - Kind name to parse.
137 ///
138 /// # Returns
139 ///
140 /// The matching top-level kind.
141 ///
142 /// # Errors
143 ///
144 /// Returns a static diagnostic when `value` is not a known kind name.
145 fn from_str(value: &str) -> Result<Self, Self::Err> {
146 if value.eq_ignore_ascii_case("object") {
147 Ok(Self::Object)
148 } else if value.eq_ignore_ascii_case("array") {
149 Ok(Self::Array)
150 } else if value.eq_ignore_ascii_case("other") {
151 Ok(Self::Other)
152 } else {
153 Err("unknown JsonRootKind")
154 }
155 }
156}