use crate::idl;
pub const fn assert_params_declared(params: &[&idl::Parameter<'_>], declared: &[&str]) {
let mut i = 0;
while i < params.len() {
assert_type_declared(params[i].ty(), declared);
i += 1;
}
}
pub const fn assert_type_declared(ty: &idl::Type<'_>, declared: &[&str]) {
if let Some(name) = custom_type_name(ty)
&& !name_in_list(name, declared)
{
panic!(
"custom type used in method signature is not declared in `types = [...]`; \
add it to the `types` list on the `#[zlink(interface = \"...\", types = [...])]` \
attribute"
);
}
}
pub const fn custom_type_name<'a>(ty: &'a idl::Type<'a>) -> Option<&'a str> {
match ty {
idl::Type::Custom(name) => Some(name),
idl::Type::Optional(inner) | idl::Type::Array(inner) | idl::Type::Map(inner) => {
custom_type_name(inner.inner())
}
_ => None,
}
}
const fn bytes_eq(a: &[u8], b: &[u8]) -> bool {
if a.len() != b.len() {
return false;
}
let mut i = 0;
while i < a.len() {
if a[i] != b[i] {
return false;
}
i += 1;
}
true
}
const fn str_eq(a: &str, b: &str) -> bool {
bytes_eq(a.as_bytes(), b.as_bytes())
}
const fn name_in_list(name: &str, declared: &[&str]) -> bool {
let mut i = 0;
while i < declared.len() {
if str_eq(name, declared[i]) {
return true;
}
i += 1;
}
false
}
#[cfg(test)]
mod tests {
use super::*;
use crate::idl::{Type, TypeRef};
#[test]
fn primitive_types_always_pass() {
const _: () = {
assert_type_declared(&Type::Bool, &[]);
assert_type_declared(&Type::Int, &[]);
assert_type_declared(&Type::Float, &[]);
assert_type_declared(&Type::String, &[]);
assert_type_declared(&Type::ForeignObject, &[]);
assert_type_declared(&Type::Any, &[]);
};
}
#[test]
fn custom_type_passes_when_declared() {
const _: () = {
assert_type_declared(&Type::Custom("Book"), &["Book", "Album"]);
assert_type_declared(&Type::Custom("Album"), &["Book", "Album"]);
};
}
#[test]
fn nested_custom_type_passes_when_declared() {
static BOOK: Type<'static> = Type::Custom("Book");
assert_type_declared(&Type::Optional(TypeRef::new(&BOOK)), &["Book"]);
assert_type_declared(&Type::Array(TypeRef::new(&BOOK)), &["Book"]);
assert_type_declared(&Type::Map(TypeRef::new(&BOOK)), &["Book"]);
}
#[test]
fn custom_type_name_extracts_correctly() {
assert!(custom_type_name(&Type::Custom("Book")).is_some());
assert!(custom_type_name(&Type::Int).is_none());
assert!(custom_type_name(&Type::String).is_none());
static BOOK: Type<'static> = Type::Custom("Book");
assert!(custom_type_name(&Type::Optional(TypeRef::new(&BOOK))).is_some());
assert!(custom_type_name(&Type::Array(TypeRef::new(&BOOK))).is_some());
}
#[test]
#[should_panic(expected = "not declared in `types = [...]`")]
fn undeclared_custom_type_panics() {
assert_type_declared(&Type::Custom("Missing"), &["Book", "Album"]);
}
#[test]
#[should_panic(expected = "not declared in `types = [...]`")]
fn undeclared_nested_custom_type_panics() {
static MISSING: Type<'static> = Type::Custom("Missing");
assert_type_declared(&Type::Optional(TypeRef::new(&MISSING)), &["Book"]);
}
}