Skip to main content

jsonschema_value/
types.rs

1//! JSON type representations for schema validation.
2//!
3//! Provides [`JsonType`] for individual types and [`JsonTypeSet`] for efficient
4//! bitset-based type checking in validation hot paths.
5
6use core::fmt;
7use std::str::FromStr;
8
9use serde_json::Value;
10
11use crate::{Json, JsonNode};
12
13/// Represents a JSON value type.
14///
15/// Discriminant order is `Null < Boolean < Integer < Number < String < Array < Object`
16/// (primitives before compounds, integers before numbers); [`JsonTypeSet`] iterates in this order.
17#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
18#[repr(u8)]
19pub enum JsonType {
20    Null = 1 << 0,
21    Boolean = 1 << 1,
22    Integer = 1 << 2,
23    Number = 1 << 3,
24    String = 1 << 4,
25    Array = 1 << 5,
26    Object = 1 << 6,
27}
28
29impl fmt::Display for JsonType {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        f.write_str(self.as_str())
32    }
33}
34
35impl JsonType {
36    #[must_use]
37    pub fn as_str(self) -> &'static str {
38        match self {
39            JsonType::Array => "array",
40            JsonType::Boolean => "boolean",
41            JsonType::Integer => "integer",
42            JsonType::Null => "null",
43            JsonType::Number => "number",
44            JsonType::Object => "object",
45            JsonType::String => "string",
46        }
47    }
48
49    pub(crate) fn from_repr(repr: u8) -> Self {
50        match repr {
51            1 => JsonType::Null,
52            2 => JsonType::Boolean,
53            4 => JsonType::Integer,
54            8 => JsonType::Number,
55            16 => JsonType::String,
56            32 => JsonType::Array,
57            64 => JsonType::Object,
58            _ => panic!("Invalid JsonType representation: {repr}"),
59        }
60    }
61}
62
63impl From<&Value> for JsonType {
64    fn from(instance: &Value) -> Self {
65        match instance {
66            Value::Null => JsonType::Null,
67            Value::Bool(_) => JsonType::Boolean,
68            Value::Number(_) => JsonType::Number,
69            Value::String(_) => JsonType::String,
70            Value::Array(_) => JsonType::Array,
71            Value::Object(_) => JsonType::Object,
72        }
73    }
74}
75
76impl FromStr for JsonType {
77    type Err = ();
78
79    fn from_str(s: &str) -> Result<Self, Self::Err> {
80        match s {
81            "array" => Ok(JsonType::Array),
82            "boolean" => Ok(JsonType::Boolean),
83            "integer" => Ok(JsonType::Integer),
84            "null" => Ok(JsonType::Null),
85            "number" => Ok(JsonType::Number),
86            "object" => Ok(JsonType::Object),
87            "string" => Ok(JsonType::String),
88            _ => Err(()),
89        }
90    }
91}
92
93/// A set of JSON types.
94#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
95pub struct JsonTypeSet(u8);
96
97impl Default for JsonTypeSet {
98    fn default() -> Self {
99        Self::empty()
100    }
101}
102
103impl From<JsonType> for JsonTypeSet {
104    #[inline]
105    fn from(ty: JsonType) -> Self {
106        Self(ty as u8)
107    }
108}
109
110impl std::ops::BitOr for JsonType {
111    type Output = JsonTypeSet;
112    #[inline]
113    fn bitor(self, rhs: JsonType) -> JsonTypeSet {
114        JsonTypeSet::from(self).insert(rhs)
115    }
116}
117
118impl std::ops::BitOr<JsonType> for JsonTypeSet {
119    type Output = JsonTypeSet;
120    #[inline]
121    fn bitor(self, rhs: JsonType) -> JsonTypeSet {
122        self.insert(rhs)
123    }
124}
125
126impl JsonTypeSet {
127    /// Create an empty set of types.
128    #[inline]
129    #[must_use]
130    pub const fn empty() -> Self {
131        Self(0)
132    }
133    /// Create a set with all possible JSON types.
134    #[inline]
135    #[must_use]
136    pub const fn all() -> Self {
137        JsonTypeSet::empty()
138            .insert(JsonType::Null)
139            .insert(JsonType::Boolean)
140            .insert(JsonType::Integer)
141            .insert(JsonType::Number)
142            .insert(JsonType::String)
143            .insert(JsonType::Array)
144            .insert(JsonType::Object)
145    }
146    /// Add a type to this set and return the modified set.
147    #[inline]
148    #[must_use]
149    pub const fn insert(mut self, ty: JsonType) -> Self {
150        self.0 |= ty as u8;
151        self
152    }
153    /// Remove a type from this set and return the modified set.
154    #[inline]
155    #[must_use]
156    pub const fn remove(mut self, ty: JsonType) -> Self {
157        self.0 &= !(ty as u8);
158        self
159    }
160    /// Types in both sets.
161    #[inline]
162    #[must_use]
163    pub const fn intersect(self, other: Self) -> Self {
164        Self(self.0 & other.0)
165    }
166    /// Types in either set.
167    #[inline]
168    #[must_use]
169    pub const fn union(self, other: Self) -> Self {
170        Self(self.0 | other.0)
171    }
172    /// Return the number of types in this set.
173    #[inline]
174    #[must_use]
175    pub const fn len(self) -> usize {
176        self.0.count_ones() as usize
177    }
178    /// Return `true` if the set contains no types.
179    #[inline]
180    #[must_use]
181    pub const fn is_empty(self) -> bool {
182        self.0 == 0
183    }
184    /// Check if this set includes the specified type.
185    #[inline]
186    #[must_use]
187    pub fn contains(self, ty: JsonType) -> bool {
188        self.0 & ty as u8 != 0
189    }
190    /// Whether a JSON value's type is allowed by this set.
191    #[must_use]
192    pub fn contains_value_type<F: Json>(self, value: &F::Node<'_>) -> bool {
193        match value.json_type() {
194            JsonType::Number => match value.as_number() {
195                // Integers satisfy both `integer` and `number`; non-integers only `number`.
196                Some(n) if number_is_integer(&n) => {
197                    self.contains(JsonType::Integer) || self.contains(JsonType::Number)
198                }
199                Some(_) => self.contains(JsonType::Number),
200                None => false,
201            },
202            other => self.contains(other),
203        }
204    }
205    /// Get an iterator over the types in this set.
206    #[inline]
207    #[must_use]
208    pub fn iter(&self) -> JsonTypeSetIterator {
209        JsonTypeSetIterator { set: *self }
210    }
211}
212
213/// Whether `n` holds an integer value per drafts 6+ (floats with a zero fractional part count as integers).
214#[must_use]
215pub fn number_is_integer(n: &serde_json::Number) -> bool {
216    #[cfg(feature = "arbitrary-precision")]
217    {
218        use crate::ext::numeric::bignum;
219        use num_traits::One;
220
221        // Important: check BigFraction BEFORE as_f64() to avoid precision loss.
222        n.is_i64()
223            || n.is_u64()
224            || if bignum::try_parse_bigint(n).is_some() {
225                true
226            } else if let Some(bigfrac) = bignum::try_parse_bigfraction(n) {
227                bigfrac.denom().is_none_or(One::is_one)
228            } else if let Some(f) = n.as_f64() {
229                f.fract() == 0.
230            } else {
231                // Numbers that overflow to infinity (as_f64() returns None).
232                false
233            }
234    }
235    #[cfg(not(feature = "arbitrary-precision"))]
236    {
237        if n.is_i64() || n.is_u64() {
238            true
239        } else if let Some(f) = n.as_f64() {
240            f.fract() == 0.
241        } else {
242            unreachable!("Numbers always fit in u64/i64/f64 without arbitrary-precision")
243        }
244    }
245}
246
247impl IntoIterator for &JsonTypeSet {
248    type Item = JsonType;
249    type IntoIter = JsonTypeSetIterator;
250    fn into_iter(self) -> Self::IntoIter {
251        self.iter()
252    }
253}
254
255impl IntoIterator for JsonTypeSet {
256    type Item = JsonType;
257    type IntoIter = JsonTypeSetIterator;
258
259    fn into_iter(self) -> Self::IntoIter {
260        JsonTypeSetIterator { set: self }
261    }
262}
263
264impl fmt::Debug for JsonTypeSet {
265    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
266        write!(f, "(")?;
267
268        let mut iter = self.iter();
269
270        if let Some(ty) = iter.next() {
271            write!(f, "{ty}")?;
272        }
273
274        for ty in iter {
275            write!(f, ", {ty}")?;
276        }
277
278        write!(f, ")")
279    }
280}
281
282/// Iterator for traversing the types in a `JsonTypeSet`.
283#[derive(Debug)]
284pub struct JsonTypeSetIterator {
285    set: JsonTypeSet,
286}
287
288impl Iterator for JsonTypeSetIterator {
289    type Item = JsonType;
290
291    fn next(&mut self) -> Option<Self::Item> {
292        if self.set.0 == 0 {
293            None
294        } else {
295            // Find the least significant bit that is set
296            let lsb = self.set.0 & self.set.0.wrapping_neg();
297
298            // Clear the least significant bit
299            self.set.0 &= self.set.0 - 1;
300
301            Some(JsonType::from_repr(lsb))
302        }
303    }
304    fn size_hint(&self) -> (usize, Option<usize>) {
305        let count = self.set.0.count_ones() as usize;
306        (count, Some(count))
307    }
308}
309
310impl ExactSizeIterator for JsonTypeSetIterator {}
311
312#[cfg(test)]
313mod tests {
314    use super::*;
315    use serde_json::json;
316    use test_case::test_case;
317
318    #[test]
319    fn type_bitor_builds_set() {
320        let set = JsonType::String | JsonType::Integer;
321        assert!(set.contains(JsonType::String));
322        assert!(set.contains(JsonType::Integer));
323        assert!(!set.contains(JsonType::Null));
324        let extended = set | JsonType::Null;
325        assert_eq!(
326            extended,
327            JsonType::String | JsonType::Integer | JsonType::Null
328        );
329        assert!(extended.contains(JsonType::Null));
330    }
331
332    #[test_case("array" => Ok(JsonType::Array) ; "parse array")]
333    #[test_case("boolean" => Ok(JsonType::Boolean) ; "parse boolean")]
334    #[test_case("integer" => Ok(JsonType::Integer) ; "parse integer")]
335    #[test_case("null" => Ok(JsonType::Null) ; "parse null")]
336    #[test_case("number" => Ok(JsonType::Number) ; "parse number")]
337    #[test_case("object" => Ok(JsonType::Object) ; "parse object")]
338    #[test_case("string" => Ok(JsonType::String) ; "parse string")]
339    #[test_case("invalid" => Err(()) ; "parse invalid")]
340    fn test_from_str(input: &str) -> Result<JsonType, ()> {
341        JsonType::from_str(input)
342    }
343
344    #[test_case(JsonType::Array => "array" ; "display array")]
345    #[test_case(JsonType::Boolean => "boolean" ; "display boolean")]
346    #[test_case(JsonType::Integer => "integer" ; "display integer")]
347    #[test_case(JsonType::Null => "null" ; "display null")]
348    #[test_case(JsonType::Number => "number" ; "display number")]
349    #[test_case(JsonType::Object => "object" ; "display object")]
350    #[test_case(JsonType::String => "string" ; "display string")]
351    fn test_display(json_type: JsonType) -> String {
352        json_type.to_string()
353    }
354
355    #[test_case(&json!(null) => JsonType::Null ; "value null")]
356    #[test_case(&json!(true) => JsonType::Boolean ; "value boolean")]
357    #[test_case(&json!(42) => JsonType::Number ; "value number int")]
358    #[test_case(&json!(1.12) => JsonType::Number ; "value number float")]
359    #[test_case(&json!("hello") => JsonType::String ; "value string")]
360    #[test_case(&json!([1, 2, 3]) => JsonType::Array ; "value array")]
361    #[test_case(&json!({"key": "value"}) => JsonType::Object ; "value object")]
362    fn test_from_value(value: &Value) -> JsonType {
363        JsonType::from(value)
364    }
365
366    #[test]
367    fn test_insert_types() {
368        let mut set = JsonTypeSet::empty();
369        set = set.insert(JsonType::String);
370        assert!(set.contains(JsonType::String));
371        assert!(!set.contains(JsonType::Number));
372
373        set = set.insert(JsonType::Number);
374        assert!(set.contains(JsonType::String));
375        assert!(set.contains(JsonType::Number));
376        assert!(!set.contains(JsonType::Array));
377    }
378
379    #[test]
380    fn test_from_json_type() {
381        let set = JsonTypeSet::from(JsonType::String);
382        assert!(set.contains(JsonType::String));
383        assert_eq!(set.len(), 1);
384    }
385
386    #[cfg(feature = "serde_json")]
387    #[test_case(&json!(null), JsonType::Null.into() => true ; "null type")]
388    #[test_case(&json!(true), JsonType::Boolean.into() => true ; "boolean type")]
389    #[test_case(&json!("test"), JsonType::String.into() => true ; "string type")]
390    #[test_case(&json!([1,2]), JsonType::Array.into() => true ; "array type")]
391    #[test_case(&json!({"a": 1}), JsonType::Object.into() => true ; "object type")]
392    #[test_case(&json!(42), JsonType::Number.into() => true ; "number matches number")]
393    #[test_case(&json!(42), JsonType::Integer.into() => true ; "int matches integer")]
394    #[test_case(&json!(1.23), JsonType::Number.into() => true ; "float matches number")]
395    #[test_case(&json!(1.23), JsonType::Integer.into() => false ; "float doesn't match integer")]
396    fn test_contains_value_type(value: &Value, set: JsonTypeSet) -> bool {
397        set.contains_value_type::<crate::SerdeJson>(&value)
398    }
399
400    #[test]
401    fn test_remove_types() {
402        let set = JsonTypeSet::all().remove(JsonType::Number);
403        assert!(!set.contains(JsonType::Number));
404        assert!(set.contains(JsonType::Integer));
405        assert_eq!(set.len(), 6);
406
407        let empty = JsonTypeSet::empty();
408        assert_eq!(empty.remove(JsonType::Boolean), empty);
409    }
410
411    #[test]
412    fn test_len() {
413        let empty = JsonTypeSet::empty();
414        assert!(empty.is_empty());
415        assert_eq!(empty.len(), 0);
416
417        let with_string = empty.insert(JsonType::String);
418        assert!(!with_string.is_empty());
419        assert_eq!(with_string.len(), 1);
420        assert_eq!(JsonTypeSet::all().len(), 7);
421    }
422
423    #[test]
424    fn test_debug_format() {
425        assert_eq!(format!("{:?}", JsonTypeSet::default()), "()");
426        assert_eq!(
427            format!("{:?}", JsonTypeSet::from(JsonType::String)),
428            "(string)"
429        );
430        assert_eq!(
431            format!(
432                "{:?}",
433                JsonTypeSet::from(JsonType::String).insert(JsonType::Number)
434            ),
435            "(number, string)"
436        );
437    }
438
439    #[test]
440    fn test_empty_iterator() {
441        let set = JsonTypeSet::empty();
442        let mut iter = set.iter();
443        assert_eq!(iter.next(), None);
444        assert_eq!(iter.size_hint(), (0, Some(0)));
445    }
446
447    #[test]
448    fn test_single_type_iterator() {
449        let set = JsonTypeSet::from(JsonType::String);
450        let mut iter = set.iter();
451        assert_eq!(iter.size_hint(), (1, Some(1)));
452        assert_eq!(iter.next(), Some(JsonType::String));
453        assert_eq!(iter.size_hint(), (0, Some(0)));
454        assert_eq!(iter.next(), None);
455        assert_eq!(iter.size_hint(), (0, Some(0)));
456    }
457
458    #[test]
459    fn test_multiple_types_iterator() {
460        let set = JsonTypeSet::from(JsonType::String)
461            .insert(JsonType::Number)
462            .insert(JsonType::Boolean);
463
464        let types: Vec<JsonType> = set.iter().collect();
465        assert_eq!(types.len(), 3);
466        assert!(types.contains(&JsonType::String));
467        assert!(types.contains(&JsonType::Number));
468        assert!(types.contains(&JsonType::Boolean));
469
470        assert_eq!(set.iter().size_hint(), (3, Some(3)));
471    }
472
473    #[test]
474    fn test_all_types_iterator() {
475        let set = JsonTypeSet::all();
476
477        let types: Vec<JsonType> = set.iter().collect();
478        assert_eq!(types.len(), 7);
479
480        let mut iter = set.iter();
481        assert_eq!(iter.len(), 7);
482        iter.next();
483        assert_eq!(iter.len(), 6);
484    }
485}