use crate::tree::{DeclaredType, TypeName};
use serde_json::Value as Json;
use std::collections::BTreeMap;
use std::fmt;
use std::rc::Rc;
#[derive(Clone, Debug, PartialEq)]
pub enum Type {
Int,
Uint,
Double,
Bool,
String,
Null,
Timestamp,
Duration,
Grammar(&'static str),
List(Rc<Type>),
Map(Rc<Type>, Rc<Type>),
Record(Rc<BTreeMap<String, Type>>),
Dyn,
}
impl Type {
#[must_use]
pub fn list(element: Self) -> Self {
Self::List(Rc::new(element))
}
#[must_use]
pub fn map(key: Self, value: Self) -> Self {
Self::Map(Rc::new(key), Rc::new(value))
}
#[must_use]
pub fn record(fields: BTreeMap<String, Self>) -> Self {
Self::Record(Rc::new(fields))
}
#[must_use]
pub fn of_json(value: &Json) -> Self {
match value {
Json::Null => Self::Null,
Json::Bool(_) => Self::Bool,
Json::Number(_) => Self::Double,
Json::String(_) => Self::String,
Json::Array(items) => Self::list(Self::joined(items.iter().map(Self::of_json))),
Json::Object(fields) => Self::record(
fields
.iter()
.map(|(key, value)| (key.clone(), Self::of_json(value)))
.collect(),
),
}
}
#[must_use]
pub fn declared(declared: &DeclaredType) -> Self {
match declared {
DeclaredType::Enum { .. } => Self::String,
DeclaredType::Input { input } => Self::declared(input),
DeclaredType::Name(name) => match name {
TypeName::String | TypeName::Date | TypeName::Name | TypeName::Enum => Self::String,
TypeName::Number => Self::Double,
TypeName::Boolean => Self::Bool,
TypeName::Array => Self::list(Self::Dyn),
TypeName::Object => Self::map(Self::String, Self::Dyn),
TypeName::Length
| TypeName::Space
| TypeName::Color
| TypeName::Tracks
| TypeName::Areas
| TypeName::Surface
| TypeName::Border
| TypeName::Radius => Self::Grammar(name.name()),
},
DeclaredType::Other(_) => Self::Dyn,
}
}
pub(crate) fn joined(types: impl IntoIterator<Item = Self>) -> Self {
types.into_iter().reduce(Self::join).unwrap_or(Self::Dyn)
}
#[must_use]
pub fn join(self, other: Self) -> Self {
match (self, other) {
(a, b) if a == b => a,
(Self::Null, other) | (other, Self::Null) => other,
(Self::Record(a), Self::Record(b)) => {
let mut fields = (*a).clone();
for (name, ty) in b.iter() {
let joined = match fields.remove(name) {
Some(known) => known.join(ty.clone()),
None => ty.clone(),
};
fields.insert(name.clone(), joined);
}
Self::record(fields)
}
(Self::List(a), Self::List(b)) => Self::list((*a).clone().join((*b).clone())),
(Self::Map(k, v), Self::Map(l, w)) => Self::map(
(*k).clone().join((*l).clone()),
(*v).clone().join((*w).clone()),
),
(Self::Grammar(_), Self::String) | (Self::String, Self::Grammar(_)) => Self::String,
_ => Self::Dyn,
}
}
#[must_use]
pub fn is_numeric(&self) -> bool {
matches!(self, Self::Int | Self::Uint | Self::Double | Self::Dyn)
}
#[must_use]
pub fn fits(&self, expected: &Self) -> bool {
match (self, expected) {
(Self::Dyn, _) | (_, Self::Dyn) | (Self::String, Self::Grammar(_)) => true,
(Self::List(a), Self::List(b)) => a.fits(b),
(Self::Map(k, v), Self::Map(l, w)) => k.fits(l) && v.fits(w),
(Self::Record(fields), Self::Map(key, value)) => {
Self::String.fits(key) && fields.values().all(|field| field.fits(value))
}
(a, b) => a == b,
}
}
}
impl fmt::Display for Type {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Int => f.write_str("int"),
Self::Uint => f.write_str("uint"),
Self::Double => f.write_str("double"),
Self::Bool => f.write_str("bool"),
Self::String => f.write_str("string"),
Self::Null => f.write_str("null"),
Self::Timestamp => f.write_str("timestamp"),
Self::Duration => f.write_str("duration"),
Self::Grammar(name) => f.write_str(name),
Self::List(element) => write!(f, "list({element})"),
Self::Map(key, value) => write!(f, "map({key}, {value})"),
Self::Record(_) => f.write_str("map(string, dyn)"),
Self::Dyn => f.write_str("dyn"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn record(fields: &[(&str, Type)]) -> Type {
Type::record(
fields
.iter()
.map(|(k, t)| ((*k).to_string(), t.clone()))
.collect(),
)
}
#[test]
fn json_numbers_are_doubles_and_objects_records() {
assert_eq!(Type::of_json(&json!(3)), Type::Double);
assert_eq!(Type::of_json(&json!(null)), Type::Null);
assert_eq!(Type::of_json(&json!(true)), Type::Bool);
assert_eq!(Type::of_json(&json!("a")), Type::String);
assert_eq!(Type::of_json(&json!([])), Type::list(Type::Dyn));
assert_eq!(Type::of_json(&json!([1, null])), Type::list(Type::Double));
assert_eq!(Type::of_json(&json!([1, "a"])), Type::list(Type::Dyn));
assert_eq!(
Type::of_json(&json!({ "a": 1, "b": [true] })),
record(&[("a", Type::Double), ("b", Type::list(Type::Bool))])
);
}
#[test]
fn records_of_data_join_all_their_fields() {
let rows = Type::of_json(&json!([
{ "name": "a", "note": "x" },
{ "name": "b", "amount": 2 },
{ "name": null, "amount": 3 },
]));
assert_eq!(
rows,
Type::list(record(&[
("amount", Type::Double),
("name", Type::String),
("note", Type::String),
]))
);
assert_eq!(
Type::of_json(&json!([{ "a": 1 }, { "a": "x" }])),
Type::list(record(&[("a", Type::Dyn)]))
);
assert_eq!(
Type::map(Type::String, Type::Int).join(Type::map(Type::String, Type::Double)),
Type::map(Type::String, Type::Dyn)
);
assert_eq!(
Type::of_json(&json!([[1], ["a"]])),
Type::list(Type::list(Type::Dyn))
);
assert_eq!(Type::Grammar("length").join(Type::String), Type::String);
assert_eq!(Type::String.join(Type::Grammar("color")), Type::String);
assert_eq!(
Type::Grammar("length").join(Type::Grammar("color")),
Type::Dyn
);
}
#[test]
fn declarations_give_types() {
let name = |n: &str| Type::declared(&DeclaredType::named(n));
assert_eq!(name("string"), Type::String);
assert_eq!(name("date"), Type::String);
assert_eq!(name("name"), Type::String);
assert_eq!(name("number"), Type::Double);
assert_eq!(name("boolean"), Type::Bool);
assert_eq!(name("array"), Type::list(Type::Dyn));
assert_eq!(name("object"), Type::map(Type::String, Type::Dyn));
assert_eq!(name("length"), Type::Grammar("length"));
assert_eq!(name("radius").to_string(), "radius");
assert_eq!(name("nonsense"), Type::Dyn);
assert_eq!(
Type::declared(&DeclaredType::Enum {
members: vec!["a".into()]
}),
Type::String
);
let input = |inner: &str| DeclaredType::Input {
input: Box::new(DeclaredType::named(inner)),
};
assert_eq!(Type::declared(&input("number")), Type::Double);
assert_eq!(Type::declared(&input("enum")), Type::String);
}
#[test]
fn types_fit_where_values_can_go() {
assert!(Type::Int.fits(&Type::Int));
assert!(!Type::Int.fits(&Type::Double));
assert!(Type::Dyn.fits(&Type::Int) && Type::Int.fits(&Type::Dyn));
assert!(Type::String.fits(&Type::Grammar("length")));
assert!(!Type::Grammar("length").fits(&Type::String));
assert!(Type::list(Type::Int).fits(&Type::list(Type::Dyn)));
assert!(!Type::list(Type::Int).fits(&Type::list(Type::String)));
assert!(Type::map(Type::String, Type::Int).fits(&Type::map(Type::String, Type::Dyn)));
assert!(record(&[("a", Type::Int)]).fits(&Type::map(Type::String, Type::Dyn)));
assert!(!record(&[("a", Type::Int)]).fits(&Type::map(Type::String, Type::String)));
assert!(!record(&[]).fits(&Type::map(Type::Int, Type::Dyn)));
assert!(!Type::Null.fits(&Type::Int));
}
#[test]
fn types_are_written_as_cel_writes_them() {
let written: Vec<String> = [
Type::Int,
Type::Uint,
Type::Double,
Type::Bool,
Type::String,
Type::Null,
Type::Timestamp,
Type::Duration,
Type::list(Type::Int),
Type::map(Type::String, Type::Dyn),
record(&[]),
]
.iter()
.map(ToString::to_string)
.collect();
assert_eq!(
written,
[
"int",
"uint",
"double",
"bool",
"string",
"null",
"timestamp",
"duration",
"list(int)",
"map(string, dyn)",
"map(string, dyn)"
]
);
}
}