Skip to main content

ifc_validate/
header.rs

1//! Header well-formedness and the schema a file declares.
2//!
3//! # What survives to here, and what does not
4//!
5//! [`ifc_model::header::Header`] is normalized: it holds a description list,
6//! an author list, a schema token list. A STEP `FILE_NAME` entry with too
7//! many parameters, or with an integer where a string belongs, is a *syntax*
8//! defect -- the codec either recovers from it or fails, and by the time a
9//! `Model` exists the original arity is gone.
10//!
11//! So this module deliberately does not attempt to re-derive header arity
12//! errors. It checks what the normalized header can still answer:
13//!
14//! - is a schema declared at all, and is it one this build can validate?
15//! - is the implementation level the Part 21 value?
16//!
17//! A caller that needs header syntax diagnostics wants them from the codec's
18//! own diagnostic channel, which sees the file. Claiming to check them here
19//! would report "clean" for a file this crate never actually inspected.
20
21use ifc_model::Model;
22use ifc_schema::SchemaVersion;
23
24use crate::report::{Finding, Path, Report};
25
26/// Check the declared schema and implementation level.
27pub fn check(model: &Model, report: &mut Report) {
28    let header = model.header();
29    match header.schema_token() {
30        None => report.push(Finding::error(
31            "header.schema.missing",
32            Path::File,
33            "FILE_SCHEMA declares no schema; the file cannot be validated \
34             against anything",
35        )),
36        Some(token) if SchemaVersion::from_header_token(token).is_none() => {
37            report.push(Finding::warning(
38                "header.schema.unknown",
39                Path::File,
40                format!(
41                    "schema {token:?} is not one this build recognizes; \
42                     structural checks will be skipped"
43                ),
44            ));
45        }
46        Some(_) => {}
47    }
48
49    // Part 21 fixes this to "2;1". A different value is not fatal -- files
50    // carrying it parse fine -- but it signals a producer that is not
51    // following the standard, which usually means other things are off too.
52    let level = &header.implementation_level;
53    if !level.is_empty() && level != "2;1" {
54        report.push(Finding::warning(
55            "header.implementation_level",
56            Path::File,
57            format!("implementation level is {level:?}; ISO 10303-21 specifies \"2;1\""),
58        ));
59    }
60}
61
62/// The schema version a model declares, if this build recognizes it.
63#[must_use]
64pub fn declared_version(model: &Model) -> Option<SchemaVersion> {
65    model
66        .header()
67        .schema_token()
68        .and_then(SchemaVersion::from_header_token)
69}