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}