json_shape/
lib.rs

1#![doc = include_str!("../README.md")]
2#![allow(clippy::redundant_pub_crate)]
3/// Module containing Error types
4pub mod error;
5mod value;
6
7mod lexer;
8pub(crate) mod parser;
9pub(crate) mod shape;
10
11use std::str::FromStr;
12
13use crate::{
14    error::Error,
15    parser::Parser,
16    shape::{merger::merge, parse_cst},
17    value::Value,
18};
19
20pub use value::Similar;
21pub use value::Value as JsonShape;
22
23/// Creates a [`JsonShape`] from a single Json source
24/// ```
25/// use json_shape::JsonShape;
26/// use std::str::FromStr;
27///
28/// let source = "[12, 34, 56]";
29/// let json_shape = JsonShape::from_str(source).unwrap();
30/// ```
31impl FromStr for Value {
32    type Err = Error;
33
34    fn from_str(source: &str) -> Result<Self, Self::Err> {
35        let cst = Parser::parse(source, &mut Vec::new());
36
37        let value = parse_cst(&cst, source)?;
38
39        Ok(value)
40    }
41}
42
43impl Value {
44    /// Creates a [`JsonShape`] from multiple Json sources
45    ///
46    /// # Errors
47    ///
48    /// Will return `Err` if failed to parse Json or if shapes don't align.
49    pub fn from_sources(sources: &[&str]) -> Result<Self, Error> {
50        let mut diags = Vec::new();
51        let mut values = Vec::new();
52        for source in sources {
53            let cst = Parser::parse(source, &mut diags);
54
55            values.push(parse_cst(&cst, source)?);
56        }
57
58        merge(&values)
59    }
60
61    /// Checks if Json is subset of specific [`JsonShape`]
62    /// ```rust
63    /// use std::str::FromStr;
64    ///
65    /// use json_shape::{IsSubset, JsonShape};
66    /// let shape = JsonShape::Object { content: [
67    ///     ("name".to_string(), JsonShape::String { optional: false }),
68    ///     ("surname".to_string(), JsonShape::String { optional: false }),
69    ///     ("middle name".to_string(), JsonShape::String { optional: true }),
70    ///     ("age".to_string(), JsonShape::Number { optional: false }),
71    ///     ("id".to_string(), JsonShape::OneOf { variants: [
72    ///         JsonShape::Object { content: [
73    ///             ("number".to_string(), JsonShape::Number { optional: false }),
74    ///             ("state".to_string(), JsonShape::String { optional: false }),
75    ///         ].into(), optional: false },
76    ///         JsonShape::Array { r#type: Box::new(JsonShape::Number { optional: false }), optional: false }
77    ///     ].into(), optional: false })
78    /// ].into(), optional: false };
79    ///
80    /// let json = r#"{
81    /// "name": "lorem",
82    /// "surname": "ipsum",
83    /// "age": 30,
84    /// "id": {
85    ///     "number": 123456,
86    ///     "state": "st"
87    /// }
88    /// }"#;
89    ///
90    /// let shape_1 = JsonShape::from_str(json).unwrap();
91    ///
92    /// # assert!(
93    /// shape_1.is_subset(&shape)
94    /// # );
95    /// # assert!(
96    /// shape.is_superset(json)
97    /// # );
98    /// ```
99    #[must_use]
100    pub fn is_superset(&self, json: &str) -> bool {
101        let Ok(value) = Self::from_str(json) else {
102            return false;
103        };
104
105        value.is_subset(self)
106    }
107
108    /// Checks if Json is subset of specific [`JsonShape`]
109    ///
110    /// - Checked version of [`is_superset`]
111    ///
112    /// # Errors
113    ///
114    /// Returns `Err` if failed to parse Json
115    pub fn is_superset_checked(&self, json: &str) -> Result<bool, Error> {
116        let value = Self::from_str(json)?;
117
118        Ok(value.is_subset(self))
119    }
120}
121
122/// Determines if `T::self` is subset of `T`.
123/// Only implemented for [`JsonShape`]/[`Value`]. A few rule examples
124///
125/// - `JsonShape::Number` is subset of `JsonShape::Option<Number>`
126/// - `JsonShape::Null` is subset of `JsonShape::Option<Number>` and  `JsonShape::Null`
127/// - `JsonShape::Number` is subset of `JsonShape::OneOf[Number | String]`
128/// - `JsonShape::Number` is *NOT* subset of `JsonShape::Array<Number>` => `1.23 != [1.23]`
129/// - `JsonShape::Array<Number>` is subset of `JsonShape::Array<OnOf<[Number | Boolean]>>`
130/// - `JsonShape::Object{"key_a": JsonShape::Number}` is *NOT* subset of `JsonShape::Object{"key_b": JsonShape::Number}` => `key_a != key_b`
131/// - `JsonShape::Object{"key_a": JsonShape::Number}` is subset of `JsonShape::Object{"key_a": JsonShape::Option<Number>}`
132/// - `JsonShape::Object{"key_a": JsonShape::Number}` is subset of `JsonShape::Object{"key_a": JsonShape::OneOf[Number | Boolean]}`
133pub trait IsSubset {
134    /// Determines if `T::self` is subset of other `T`
135    fn is_subset(&self, other: &Self) -> bool;
136}