Skip to main content

ifc_model/
codec.rs

1//! The serialization seam.
2//!
3//! # Why a trait, and why it lives here
4//!
5//! IFC is a *data model* with several concrete encodings: STEP physical file
6//! (`.ifc`), ifcXML (`.ifcxml`), and prospectively IFC-JSON. They differ only
7//! in syntax — the entity graph is identical.
8//!
9//! Defining [`Codec`] in `ifc-model` means:
10//!
11//! - the data model never depends on any particular encoding;
12//! - a new encoding is a new crate implementing this trait, with no change
13//!   here and no change to any consumer;
14//! - conversion between encodings is free — parse with one, write with
15//!   another, because both speak [`Model`].
16//!
17//! The inverse layering (a model that depends on the STEP reader) would make
18//! ifcXML support a second parallel stack and make cross-format conversion
19//! lossy.
20
21use crate::error::ModelError;
22use crate::model::Model;
23use std::io::{Read, Write};
24use std::path::Path;
25
26/// Read and write one concrete IFC serialization.
27///
28/// Implementors are stateless; they carry configuration only.
29pub trait Codec {
30    /// Human-readable name for diagnostics, e.g. `STEP`.
31    fn name(&self) -> &'static str;
32
33    /// Conventional file extensions, lower-case, without the dot.
34    fn extensions(&self) -> &'static [&'static str];
35
36    /// Does this look like a file this codec can read?
37    ///
38    /// Content sniffing, so a file with the wrong extension still opens.
39    /// Defaults to `false` — a codec that cannot cheaply recognize its own
40    /// format should say so rather than claim every input, which would make
41    /// codec selection order-dependent.
42    fn detect(&self, _bytes: &[u8]) -> bool {
43        false
44    }
45
46    /// Parse a model from bytes.
47    ///
48    /// Bytes rather than `&str` because IFC files are not guaranteed UTF-8:
49    /// STEP escapes non-ASCII text, and a stray raw byte must not abort the
50    /// parse of an otherwise valid file.
51    fn read_bytes(&self, bytes: &[u8]) -> Result<Model, ModelError>;
52
53    /// Serialize a model.
54    fn write(&self, model: &Model, out: &mut dyn Write) -> Result<(), ModelError>;
55
56    /// Parse from any reader. Override when the format can stream.
57    fn read_from(&self, reader: &mut dyn Read) -> Result<Model, ModelError> {
58        let mut buf = Vec::new();
59        reader
60            .read_to_end(&mut buf)
61            .map_err(|e| ModelError::Io(e.to_string()))?;
62        self.read_bytes(&buf)
63    }
64
65    /// Parse a file from disk.
66    ///
67    /// Override this when the codec can memory-map instead of reading into a
68    /// heap buffer; `ifc-step` does exactly that for large models.
69    fn read_path(&self, path: &Path) -> Result<Model, ModelError> {
70        let bytes = std::fs::read(path).map_err(|e| ModelError::Io(e.to_string()))?;
71        self.read_bytes(&bytes)
72    }
73
74    /// Serialize to a file on disk.
75    fn write_path(&self, model: &Model, path: &Path) -> Result<(), ModelError> {
76        let mut file = std::fs::File::create(path).map_err(|e| ModelError::Io(e.to_string()))?;
77        self.write(model, &mut file)
78    }
79
80    /// Serialize to a byte vector.
81    fn write_bytes(&self, model: &Model) -> Result<Vec<u8>, ModelError> {
82        let mut out = Vec::new();
83        self.write(model, &mut out)?;
84        Ok(out)
85    }
86}