ifc_lite_processing/symbolic/
provenance.rs1use super::{SymbolicData, SymbolicFillArea};
5use serde::{Serialize, Serializer, ser::{SerializeSeq, SerializeStruct}};
6
7#[derive(Debug, Clone, Default)]
11pub struct SymbolicDataWithProvenance {
12 data: SymbolicData,
13 fill_items: Vec<Option<u32>>,
14}
15impl SymbolicDataWithProvenance {
16 pub(super) fn new(data: SymbolicData, fill_items: Vec<Option<u32>>) -> Self {
17 Self { data, fill_items }
18 }
19 pub fn data(&self) -> &SymbolicData { &self.data }
21 pub fn fill_items(&self) -> &[Option<u32>] { &self.fill_items }
23 pub fn into_parts(self) -> (SymbolicData, Vec<Option<u32>>) { (self.data, self.fill_items) }
25 pub fn is_empty(&self) -> bool { self.data.is_empty() }
27}
28#[derive(Serialize)]
29struct Fill<'a> {
30 #[serde(flatten)]
31 fill: &'a SymbolicFillArea,
32 #[serde(skip_serializing_if = "Option::is_none")]
33 geometry_item_id: Option<u32>,
34}
35struct Fills<'a>(&'a SymbolicDataWithProvenance);
36impl Serialize for Fills<'_> {
37 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
38 let mut seq = serializer.serialize_seq(Some(self.0.data.fills.len()))?;
39 for (ordinal, fill) in self.0.data.fills.iter().enumerate() {
40 seq.serialize_element(&Fill { fill, geometry_item_id: self.0.fill_items.get(ordinal).copied().flatten() })?;
41 }
42 seq.end()
43 }
44}
45impl Serialize for SymbolicDataWithProvenance {
46 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
47 let mut out = serializer.serialize_struct("SymbolicData", 5 + usize::from(self.data.truncated.is_some()))?;
48 out.serialize_field("grid_axes", &self.data.grid_axes)?;
49 out.serialize_field("polylines", &self.data.polylines)?;
50 out.serialize_field("circles", &self.data.circles)?;
51 out.serialize_field("texts", &self.data.texts)?;
52 out.serialize_field("fills", &Fills(self))?;
53 if let Some(truncated) = &self.data.truncated { out.serialize_field("truncated", truncated)?; }
54 out.end()
55 }
56}
57
58impl<'de> serde::Deserialize<'de> for SymbolicDataWithProvenance {
60 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
61 #[derive(serde::Deserialize)]
62 struct OwnedFill {
63 #[serde(flatten)]
64 fill: SymbolicFillArea,
65 #[serde(default)]
66 geometry_item_id: Option<u32>,
67 }
68 #[derive(serde::Deserialize)]
69 struct Wire {
70 grid_axes: Vec<super::SymbolicGridAxis>,
71 polylines: Vec<super::SymbolicPolyline>,
72 circles: Vec<super::SymbolicCircle>,
73 texts: Vec<super::SymbolicText>,
74 fills: Vec<OwnedFill>,
75 #[serde(default)]
76 truncated: Option<super::SymbolicTruncation>,
77 }
78 let wire = Wire::deserialize(deserializer)?;
79 let (fills, fill_items) = wire.fills.into_iter().map(|f| (f.fill, f.geometry_item_id)).unzip();
80 Ok(Self { data: SymbolicData { grid_axes: wire.grid_axes, polylines: wire.polylines,
81 circles: wire.circles, texts: wire.texts, fills, truncated: wire.truncated }, fill_items })
82 }
83}
84impl From<SymbolicData> for SymbolicDataWithProvenance {
85 fn from(data: SymbolicData) -> Self {
86 let fill_items = vec![None; data.fills.len()];
87 Self { data, fill_items }
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94 #[test]
95 fn issue_4459_optional_provenance_preserves_legacy_json_and_nan_sentinels() {
96 let old = r#"{"grid_axes":[],"polylines":[],"circles":[],"texts":[],"fills":[{"express_id":1,"ifc_type":"IfcAnnotation","points":[0,0,1,0,0,1],"holes_offsets":[],"fill_color":[1,0,0,1],"has_hatching":false,"hatch_spacing":0,"hatch_angle":0,"hatch_angle_secondary":null,"hatch_line_width":0,"world_y":null,"representation":"Annotation"}]}"#;
97 let legacy: SymbolicData = serde_json::from_str(old).unwrap();
98 let decoded: SymbolicDataWithProvenance = serde_json::from_str(old).unwrap();
99 assert_eq!(decoded.fill_items(), &[None]);
100 assert!(decoded.data().fills[0].world_y.is_nan());
101 assert_eq!(serde_json::to_value(&decoded).unwrap(), serde_json::to_value(&legacy).unwrap());
102 let mut accumulator = super::super::output_cap::SymbolicAccumulator::new();
103 let mut invalid = legacy.fills[0].clone();
104 invalid.points[0] = f32::NAN;
105 accumulator.push_fill_with_provenance(invalid, Some(77));
106 accumulator.push_fill_with_provenance(legacy.fills[0].clone(), Some(99));
107 let accepted = accumulator.into_provenance();
108 assert_eq!(accepted.data().fills.len(), 1);
109 assert_eq!(accepted.fill_items(), &[Some(99)], "refused appends cannot shift ordinal provenance");
110 let enriched = SymbolicDataWithProvenance::new(legacy, vec![Some(99)]);
111 let bytes = serde_json::to_vec(&enriched).unwrap();
112 let roundtrip: SymbolicDataWithProvenance = serde_json::from_slice(&bytes).unwrap();
113 assert_eq!(roundtrip.fill_items(), &[Some(99)]);
114 let old_reader: SymbolicData = serde_json::from_slice(&bytes).unwrap();
115 assert_eq!(old_reader.fills.len(), 1);
116 assert!(old_reader.fills[0].hatch_angle_secondary.is_nan());
117 }
118}