use crate::common::types::Type;
use crate::common::value::{CowVal, Val};
use crate::DeclarationError;
use crate::ExecutionError;
use std::collections::BTreeMap;
use std::fmt::{Debug, Formatter};
pub trait StructType: Send + Sync {
fn get_type(&self) -> &Type;
fn new_value<'b, 'v>(
&self,
fields: BTreeMap<String, CowVal<'b, 'v>>,
) -> Result<Box<dyn Val + 'v>, ExecutionError>;
}
pub enum TypeDecl {
Type(Type),
Struct(Box<dyn StructType>),
}
impl From<Type> for TypeDecl {
fn from(t: Type) -> Self {
TypeDecl::Type(t)
}
}
impl<T: StructType + 'static> From<T> for TypeDecl {
fn from(s: T) -> Self {
TypeDecl::Struct(Box::new(s))
}
}
#[derive(Default)]
pub struct TypeRegistry {
types: BTreeMap<String, Type>,
structs: BTreeMap<String, Box<dyn StructType>>,
}
impl Debug for TypeRegistry {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TypeRegistry")
.field("types", &self.types)
.field("structs", &self.structs.keys())
.finish()
}
}
impl TypeRegistry {
pub fn find_type(&self, name: &str) -> Option<&Type> {
self.types.get(name)
}
pub(crate) fn register(&mut self, decl: impl Into<TypeDecl>) -> Result<(), DeclarationError> {
match decl.into() {
TypeDecl::Type(t) => self.register_type(t),
TypeDecl::Struct(s) => self.register_struct(s),
}
}
fn register_type(&mut self, t: Type) -> Result<(), DeclarationError> {
if !is_qualified_ident(t.name()) {
return Err(DeclarationError::invalid_type_name(t.name()));
}
match self.types.get(t.name()) {
Some(existing) if *existing == t => Ok(()),
Some(_) => Err(DeclarationError::type_conflict(t.name())),
None => {
self.types.insert(t.name().to_owned(), t);
Ok(())
}
}
}
pub fn find_struct(&self, name: &str) -> Option<&dyn StructType> {
self.structs.get(name).map(Box::as_ref)
}
fn register_struct(&mut self, s: Box<dyn StructType>) -> Result<(), DeclarationError> {
let name = s.get_type().name();
if self.structs.contains_key(name) {
return Err(DeclarationError::type_conflict(name));
}
self.register_type(s.get_type().to_owned())?;
self.structs.insert(name.to_owned(), s);
Ok(())
}
}
pub(crate) fn is_qualified_ident(name: &str) -> bool {
name.split('.').all(|ident| {
let mut chars = ident.chars();
chars
.next()
.is_some_and(|c| c.is_ascii_alphabetic() || c == '_')
&& chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::common::types::{Kind, INT_TYPE, OPTIONAL_TYPE};
#[test]
fn a_new_registry_is_empty() {
let registry = TypeRegistry::default();
assert!(registry.find_type("int").is_none());
assert!(registry.find_type("optional_type").is_none());
}
#[test]
fn a_type_is_found_by_its_name() {
let mut registry = TypeRegistry::default();
assert_eq!(
registry.register(Type::new_opaque_type("my.pkg.Ip")),
Ok(())
);
let t = registry.find_type("my.pkg.Ip").unwrap();
assert_eq!(t.kind(), Kind::Opaque);
assert_eq!(t.name(), "my.pkg.Ip");
}
#[test]
fn registering_the_same_type_again_is_a_no_op() {
let mut registry = TypeRegistry::default();
assert_eq!(registry.register(OPTIONAL_TYPE), Ok(()));
assert_eq!(registry.register(OPTIONAL_TYPE), Ok(()));
assert_eq!(registry.find_type("optional_type"), Some(&OPTIONAL_TYPE));
}
#[test]
fn another_type_of_the_same_name_is_a_conflict() {
let mut registry = TypeRegistry::default();
registry.register(INT_TYPE).unwrap();
assert_eq!(
registry.register(Type::new_opaque_type("int")),
Err(DeclarationError::type_conflict("int"))
);
assert_eq!(registry.find_type("int"), Some(&INT_TYPE));
registry.register(Type::new_opaque_type("Ip")).unwrap();
assert_eq!(
registry.register(Type::simple_type(Kind::Opaque, "Ip")),
Ok(()),
"an equal type, however built"
);
assert_eq!(
registry.register(Type::simple_type(Kind::Unspecified, "Ip")),
Err(DeclarationError::type_conflict("Ip"))
);
}
#[test]
fn a_name_no_expression_can_refer_to_is_invalid() {
let mut registry = TypeRegistry::default();
for name in ["", ".", "a.", ".a", "a..b", "1a", "a-b", "a b", "`a`"] {
assert_eq!(
registry.register(Type::new_opaque_type(name)),
Err(DeclarationError::invalid_type_name(name)),
"{name:?}"
);
assert!(registry.find_type(name).is_none(), "{name:?}");
}
for name in ["a", "_a", "a1", "a_b.C", "my.pkg.Type"] {
assert_eq!(
registry.register(Type::new_opaque_type(name)),
Ok(()),
"{name:?}"
);
}
}
mod structs {
use super::super::*;
use crate::common::types::{Kind, INT_TYPE};
use crate::StructDef;
#[test]
fn a_struct_is_registered_with_its_type() {
let mut registry = TypeRegistry::default();
assert_eq!(
registry.register(StructDef::new("acme.Account".into())),
Ok(())
);
let s = registry.find_struct("acme.Account").unwrap();
assert_eq!(s.get_type().name(), "acme.Account");
let t = registry.find_type("acme.Account").unwrap();
assert_eq!(t.kind(), Kind::Struct);
}
#[test]
fn a_struct_of_a_registered_name_is_a_conflict() {
let mut registry = TypeRegistry::default();
registry
.register(StructDef::new("acme.Account".into()))
.unwrap();
assert_eq!(
registry.register(
StructDef::new("acme.Account".into()).add_field("id".into(), INT_TYPE)
),
Err(DeclarationError::type_conflict("acme.Account"))
);
registry.register(INT_TYPE).unwrap();
assert_eq!(
registry.register(StructDef::new("int".into())),
Err(DeclarationError::type_conflict("int"))
);
assert!(registry.find_struct("int").is_none());
assert_eq!(registry.find_type("int"), Some(&INT_TYPE));
}
#[test]
fn a_struct_of_a_registered_equal_type_is_registered() {
let mut registry = TypeRegistry::default();
registry
.register(Type::new_struct_type("acme.Account"))
.unwrap();
assert_eq!(
registry.register(StructDef::new("acme.Account".into())),
Ok(())
);
assert!(registry.find_struct("acme.Account").is_some());
}
#[test]
fn a_struct_of_an_invalid_name_is_not_registered() {
let mut registry = TypeRegistry::default();
assert_eq!(
registry.register(StructDef::new("acme-Account".into())),
Err(DeclarationError::invalid_type_name("acme-Account"))
);
assert!(registry.find_struct("acme-Account").is_none());
}
}
}