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}