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}