use super::names::{Class, SCHEMA_TYPES, Shape, classify};
use super::values::{looks_like_credential, printable_member_name};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Kind {
Null,
Bool,
Number,
String,
Array,
Object,
}
pub trait Json: Sized {
fn kind(&self) -> Kind;
fn text(&self) -> Option<&str>;
fn items(&self) -> Vec<&Self>;
fn members(&self) -> Vec<(&str, &Self)>;
}
pub fn get<'a, J: Json>(value: &'a J, name: &str) -> Option<&'a J> {
value
.members()
.into_iter()
.find(|(member, _)| *member == name)
.map(|(_, child)| child)
}
pub fn text_free<J: Json>(value: &J) -> bool {
match value.kind() {
Kind::String => false,
Kind::Array => value.items().into_iter().all(text_free),
Kind::Object => value.members().into_iter().all(|(name, child)| {
matches!(
classify(name),
Class::Plain | Class::Fingerprint | Class::Count
) && printable_member_name(name)
&& text_free(child)
}),
_ => true,
}
}
pub fn is_empty<J: Json>(value: &J) -> bool {
match value.kind() {
Kind::String => value.text().is_some_and(str::is_empty),
Kind::Array => value.items().is_empty(),
Kind::Object => value.members().is_empty(),
_ => false,
}
}
pub fn shape<J: Json>(value: &J) -> Shape {
match value.kind() {
Kind::Null => Shape::Null,
Kind::Bool => Shape::Bool,
Kind::Number => Shape::Number,
Kind::String if value.text().is_some_and(looks_like_credential) => Shape::CredentialLike,
Kind::String => Shape::String,
Kind::Array | Kind::Object if text_free(value) => Shape::TextFree,
Kind::Array | Kind::Object => Shape::Container,
}
}
pub fn is_schema<J: Json>(value: &J) -> bool {
let is_type = |name: &J| name.text().is_some_and(|name| SCHEMA_TYPES.contains(&name));
match get(value, "type") {
Some(names) if names.kind() == Kind::Array => {
let names = names.items();
!names.is_empty() && names.into_iter().all(is_type)
}
Some(name) => is_type(name),
None => false,
}
}