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    /// Parse a model from a buffer the model may keep.
54    ///
55    /// A codec that loads lazily keeps its source for the model's lifetime;
56    /// handing it the buffer avoids copying the whole input once more.
57    /// Defaults to [`Codec::read_bytes`].
58    fn read_owned(&self, bytes: Vec<u8>) -> Result<Model, ModelError> {
59        self.read_bytes(&bytes)
60    }
61
62    /// Serialize a model.
63    fn write(&self, model: &Model, out: &mut dyn Write) -> Result<(), ModelError>;
64
65    /// Parse from any reader. Override when the format can stream.
66    fn read_from(&self, reader: &mut dyn Read) -> Result<Model, ModelError> {
67        let mut buf = Vec::new();
68        reader
69            .read_to_end(&mut buf)
70            .map_err(|e| ModelError::Io(e.to_string()))?;
71        self.read_owned(buf)
72    }
73
74    /// Parse a file from disk.
75    ///
76    /// Override this when the codec can memory-map instead of reading into a
77    /// heap buffer; `ifc-step` does exactly that for large models.
78    fn read_path(&self, path: &Path) -> Result<Model, ModelError> {
79        let bytes = std::fs::read(path).map_err(|e| ModelError::Io(e.to_string()))?;
80        self.read_bytes(&bytes)
81    }
82
83    /// Serialize to a file on disk.
84    fn write_path(&self, model: &Model, path: &Path) -> Result<(), ModelError> {
85        let mut file = std::fs::File::create(path).map_err(|e| ModelError::Io(e.to_string()))?;
86        self.write(model, &mut file)
87    }
88
89    /// Serialize to a byte vector.
90    fn write_bytes(&self, model: &Model) -> Result<Vec<u8>, ModelError> {
91        let mut out = Vec::new();
92        self.write(model, &mut out)?;
93        Ok(out)
94    }
95}