use super::types::{primary_type, DtdlContent, DtdlInterface, DtdlValidationError};
pub fn validate(iface: &DtdlInterface) -> Vec<DtdlValidationError> {
let mut errors = Vec::new();
if let Err(e) = iface.id.validate() {
errors.push(e);
}
if primary_type(&iface.element_type) != Some("Interface") {
errors.push(DtdlValidationError::MissingField {
field: "@type must be 'Interface'",
});
}
for content in iface.contents.as_deref().unwrap_or(&[]) {
validate_content(content, &mut errors);
}
errors
}
fn validate_content(content: &DtdlContent, errors: &mut Vec<DtdlValidationError>) {
match content {
DtdlContent::Telemetry(t) => {
if t.name.trim().is_empty() {
errors.push(DtdlValidationError::MissingField {
field: "Telemetry.name must not be empty",
});
}
if let Some(id) = &t.id {
if let Err(e) = id.validate() {
errors.push(e);
}
}
}
DtdlContent::Property(p) => {
if p.name.trim().is_empty() {
errors.push(DtdlValidationError::MissingField {
field: "Property.name must not be empty",
});
}
if let Some(id) = &p.id {
if let Err(e) = id.validate() {
errors.push(e);
}
}
}
DtdlContent::Command(c) => {
if c.name.trim().is_empty() {
errors.push(DtdlValidationError::MissingField {
field: "Command.name must not be empty",
});
}
}
DtdlContent::Component(comp) => {
if comp.name.trim().is_empty() {
errors.push(DtdlValidationError::MissingField {
field: "Component.name must not be empty",
});
}
if let Err(e) = comp.schema.validate() {
errors.push(e);
}
}
DtdlContent::Relationship(rel) => {
if rel.name.trim().is_empty() {
errors.push(DtdlValidationError::MissingField {
field: "Relationship.name must not be empty",
});
}
if let Some(target) = &rel.target {
if let Err(e) = target.validate() {
errors.push(e);
}
}
}
}
}
pub fn is_valid(iface: &DtdlInterface) -> bool {
validate(iface).is_empty()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dtdl::parser::parse_dtdl_interface;
use crate::dtdl::types::Dtmi;
#[test]
fn valid_minimal_interface() {
let json = r#"{
"@type": "Interface",
"@id": "dtmi:test:Valid;1"
}"#;
let iface = parse_dtdl_interface(json).expect("parse");
let errors = validate(&iface);
assert!(errors.is_empty(), "unexpected errors: {errors:?}");
}
#[test]
fn invalid_dtmi_caught() {
let json = r#"{
"@type": "Interface",
"@id": "dtmi:test:Valid;1"
}"#;
let mut iface = parse_dtdl_interface(json).expect("parse");
iface.id = Dtmi("bad-id".into());
let errors = validate(&iface);
assert!(!errors.is_empty(), "expected DTMI error");
assert!(errors
.iter()
.any(|e| matches!(e, DtdlValidationError::InvalidDtmi { .. })));
}
#[test]
fn valid_telemetry_property_command() {
let json = r#"{
"@type": "Interface",
"@id": "dtmi:test:Full;1",
"contents": [
{ "@type": "Telemetry", "name": "temp", "schema": "double" },
{ "@type": "Property", "name": "target", "schema": "double", "writable": true },
{ "@type": "Command", "name": "reset" }
]
}"#;
let iface = parse_dtdl_interface(json).expect("parse");
let errors = validate(&iface);
assert!(errors.is_empty(), "{errors:?}");
}
#[test]
fn is_valid_wrapper() {
let json = r#"{"@type":"Interface","@id":"dtmi:t:X;1"}"#;
let iface = parse_dtdl_interface(json).expect("parse");
assert!(is_valid(&iface));
}
#[test]
fn valid_relationship_with_target() {
let json = r#"{
"@type": "Interface",
"@id": "dtmi:test:Building;1",
"contents": [
{ "@type": "Relationship", "name": "contains", "target": "dtmi:test:Room;1" }
]
}"#;
let iface = parse_dtdl_interface(json).expect("parse");
let errors = validate(&iface);
assert!(errors.is_empty(), "{errors:?}");
}
#[test]
fn valid_component_with_dtmi_schema() {
let json = r#"{
"@type": "Interface",
"@id": "dtmi:test:Building;1",
"contents": [
{ "@type": "Component", "name": "thermostat", "schema": "dtmi:test:Thermo;1" }
]
}"#;
let iface = parse_dtdl_interface(json).expect("parse");
let errors = validate(&iface);
assert!(errors.is_empty(), "{errors:?}");
}
#[test]
fn multiple_content_elements_all_validated() {
let json = r#"{
"@type": "Interface",
"@id": "dtmi:test:Multi;1",
"contents": [
{ "@type": "Telemetry", "name": "a", "schema": "double" },
{ "@type": "Property", "name": "b", "schema": "string" },
{ "@type": "Command", "name": "c" },
{ "@type": "Relationship", "name": "d" }
]
}"#;
let iface = parse_dtdl_interface(json).expect("parse");
let errors = validate(&iface);
assert!(errors.is_empty(), "{errors:?}");
}
}