///|
fn severity_text(severity : DiagnosticSeverity) -> String {
match severity {
Error => "error"
Warning => "warning"
Note => "note"
}
}
///|
fn diagnostic(
severity : DiagnosticSeverity,
code : String,
message : String,
entity_id : Int?,
) -> Diagnostic {
{ severity, code, message, entity_id }
}
///|
pub fn ValidationReport::is_valid(self : ValidationReport) -> Bool {
self.error_count == 0
}
///|
pub fn ValidationReport::diagnostic_codes(
self : ValidationReport,
) -> Array[String] {
self.diagnostics.map(item => item.code)
}
///|
pub fn ValidationReport::format(self : ValidationReport) -> String {
self.diagnostics
.map(item => {
let location = item.entity_id.map(id => " #" + id.to_string()).unwrap_or("")
severity_text(item.severity) +
"[" +
item.code +
"]" +
location +
": " +
item.message
})
.join("\n")
}
///|
fn add_diagnostic(
diagnostics : Array[Diagnostic],
severity : DiagnosticSeverity,
code : String,
message : String,
entity_id : Int?,
) -> Unit {
diagnostics.push(diagnostic(severity, code, message, entity_id))
}
///|
/// Check references and structural invariants without rejecting unknown IFC extensions.
pub fn validate(model : Model) -> ValidationReport {
let index = model.index()
let diagnostics : Array[Diagnostic] = []
let mut error_count = 0
let mut warning_count = 0
let seen : Map[Int, Bool] = Map([])
if model.header.schema_identifiers.length() == 0 {
add_diagnostic(diagnostics, Error, "IFC001", "FILE_SCHEMA is empty", None)
error_count += 1
}
for schema in model.header.schema_identifiers {
let upper = schema.to_upper()
if upper != "IFC2X3" &&
upper != "IFC4" &&
upper != "IFC4X1" &&
upper != "IFC4X3" {
add_diagnostic(
diagnostics,
Warning,
"IFC002",
"schema is not one of the built-in semantic profiles: " + schema,
None,
)
warning_count += 1
}
}
for entity in model.entities {
if seen.contains(entity.id) {
add_diagnostic(
diagnostics,
Error,
"IFC003",
"duplicate entity id",
Some(entity.id),
)
error_count += 1
} else {
seen[entity.id] = true
}
if entity.name == "" {
add_diagnostic(
diagnostics,
Error,
"IFC004",
"entity type is empty",
Some(entity.id),
)
error_count += 1
}
for target in entity_references(entity) {
if !index.has(target) {
add_diagnostic(
diagnostics,
Error,
"IFC005",
"reference target does not exist",
Some(entity.id),
)
error_count += 1
}
}
let upper = entity.name.to_upper()
if upper.has_prefix("IFCREL") && entity.arguments.length() < 6 {
add_diagnostic(
diagnostics,
Warning,
"IFC006",
"relationship has fewer than six common attributes",
Some(entity.id),
)
warning_count += 1
}
}
{ diagnostics, error_count, warning_count }
}
///|
pub fn Model::validate(self : Model) -> ValidationReport {
validate(self)
}
///|
pub fn validation_summary(report : ValidationReport) -> String {
if report.is_valid() {
"valid: \{report.warning_count} warning(s)"
} else {
"invalid: \{report.error_count} error(s), \{report.warning_count} warning(s)"
}
}