use ifc_model::Value;
use ifc_schema::{Schema, TypeKind};
pub(crate) fn describe_value(value: &Value) -> String {
match value {
Value::Null => "unset ($)".to_owned(),
Value::Derived => "derived (*)".to_owned(),
Value::Bool(_) => "a boolean".to_owned(),
Value::LogicalUnknown => "a logical unknown".to_owned(),
Value::Integer(_) => "an integer".to_owned(),
Value::Real(_) => "a real".to_owned(),
Value::Text(_) => "a string".to_owned(),
Value::Binary(_) => "a binary literal".to_owned(),
Value::Enum(_) => "an enumeration constant".to_owned(),
Value::Ref(_) => "an entity reference".to_owned(),
Value::List(_) => "an aggregate".to_owned(),
Value::Typed { type_name, .. } => format!("a {type_name} wrapper"),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Shape {
Number,
Text,
Logical,
Binary,
Reference,
Enumeration,
Unresolved,
}
fn shape_of(schema: &Schema, type_name: &str, depth: u8) -> Shape {
if depth == 0 {
return Shape::Unresolved;
}
match type_name.to_ascii_uppercase().as_str() {
"REAL" | "INTEGER" | "NUMBER" => return Shape::Number,
"STRING" => return Shape::Text,
"BOOLEAN" | "LOGICAL" => return Shape::Logical,
"BINARY" => return Shape::Binary,
_ => {}
}
if schema.entity(type_name).is_some() {
return Shape::Reference;
}
let Some(def) = schema.type_def(type_name) else {
return Shape::Unresolved;
};
match &def.kind {
TypeKind::Defined(alias) => shape_of(schema, alias, depth - 1),
TypeKind::Enumeration(_) => Shape::Enumeration,
TypeKind::Select(members) => {
let mut shapes = members.iter().map(|m| shape_of(schema, m, depth - 1));
let Some(first) = shapes.next() else {
return Shape::Unresolved;
};
if shapes.all(|s| s == first) {
first
} else {
Shape::Unresolved
}
}
}
}
pub(crate) fn value_matches(schema: &Schema, type_name: &str, value: &Value) -> bool {
if matches!(value, Value::Null | Value::Derived) {
return true;
}
if let Value::Typed {
type_name: wrapper,
value: inner,
} = value
{
return value_matches(schema, wrapper, inner);
}
match shape_of(schema, type_name, 16) {
Shape::Unresolved => true,
Shape::Number => matches!(value, Value::Integer(_) | Value::Real(_)),
Shape::Text => matches!(value, Value::Text(_)),
Shape::Logical => matches!(value, Value::Bool(_) | Value::LogicalUnknown),
Shape::Binary => matches!(value, Value::Binary(_)),
Shape::Reference => matches!(value, Value::Ref(_)),
Shape::Enumeration => matches!(value, Value::Enum(_)),
}
}