Skip to main content

compose_json/
compose-json.rs

1//! Composes the `data` of one or more IFCX files and prints the composed tree
2//! as JSON. `scripts/upstream-parity.sh` compares this output with upstream's
3//! TypeScript composition.
4//!
5//! ```sh
6//! cargo run --release -p openbim-ifcx --example compose-json -- base.ifcx overlay.ifcx
7//! ```
8//!
9//! Files are layers in the order given, weakest first: their `data` arrays
10//! are concatenated, flattened, and composed, as upstream's `Federate` and
11//! `LoadIfcxFile` do. Imports are not resolved; `ifcx_alpha` examples import
12//! only schema files, which carry no `data`.
13//!
14//! On success, stdout holds the artificial root as nested
15//! `{"path", "attributes", "children"}` objects, the shape of upstream's
16//! `PostCompositionNode` with `node` named `path`. Maps keep this crate's
17//! order and numbers are written as read. If composition fails, stdout holds
18//! `{"error": {"kind": "cycle" | "unknown-reference", "message": "..."}}`
19//! and the exit code is 0. Unreadable input exits with code 1.
20
21use std::io::{BufWriter, Write};
22use std::process::ExitCode;
23
24use openbim_ifcx::{compose, flatten_owned, ComposeError, ComposedNode, IfcxFile};
25use serde::ser::{Serialize, SerializeMap, Serializer};
26
27/// Serialises a composed node without building an intermediate
28/// `serde_json::Value` for the whole tree.
29struct Tree<'a>(&'a ComposedNode);
30
31struct Attributes<'a>(&'a ComposedNode);
32
33struct Children<'a>(&'a ComposedNode);
34
35impl Serialize for Tree<'_> {
36    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
37        let mut map = serializer.serialize_map(Some(3))?;
38        map.serialize_entry("path", &self.0.path)?;
39        map.serialize_entry("attributes", &Attributes(self.0))?;
40        map.serialize_entry("children", &Children(self.0))?;
41        map.end()
42    }
43}
44
45impl Serialize for Attributes<'_> {
46    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
47        let mut map = serializer.serialize_map(Some(self.0.attributes.len()))?;
48        for (id, value) in &self.0.attributes {
49            map.serialize_entry(id, &**value)?;
50        }
51        map.end()
52    }
53}
54
55impl Serialize for Children<'_> {
56    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
57        let mut map = serializer.serialize_map(Some(self.0.children.len()))?;
58        for (name, child) in &self.0.children {
59            map.serialize_entry(name, &Tree(child))?;
60        }
61        map.end()
62    }
63}
64
65fn main() -> ExitCode {
66    let paths: Vec<String> = std::env::args().skip(1).collect();
67    if paths.is_empty() {
68        eprintln!("usage: compose-json <file.ifcx>...");
69        return ExitCode::FAILURE;
70    }
71    let mut data = Vec::new();
72    for path in &paths {
73        let file = std::fs::read(path)
74            .map_err(|e| e.to_string())
75            .and_then(|bytes| IfcxFile::from_json_slice(&bytes).map_err(|e| e.to_string()));
76        match file {
77            Ok(file) => data.extend(file.data),
78            Err(e) => {
79                eprintln!("{path}: {e}");
80                return ExitCode::FAILURE;
81            }
82        }
83    }
84
85    let stdout = std::io::stdout().lock();
86    let mut out = BufWriter::new(stdout);
87    let written = match compose(&flatten_owned(data)) {
88        Ok(composed) => serde_json::to_writer(&mut out, &Tree(&composed.root())),
89        Err(e) => {
90            let kind = match e {
91                ComposeError::Cycle { .. } => "cycle",
92                ComposeError::UnknownReference { .. } => "unknown-reference",
93                _ => "other",
94            };
95            let error = serde_json::json!({"error": {"kind": kind, "message": e.to_string()}});
96            serde_json::to_writer(&mut out, &error)
97        }
98    };
99    match written.map_err(std::io::Error::from).and_then(|()| {
100        out.write_all(b"\n")?;
101        out.flush()
102    }) {
103        Ok(()) => ExitCode::SUCCESS,
104        Err(e) => {
105            eprintln!("writing output: {e}");
106            ExitCode::FAILURE
107        }
108    }
109}