ifc_lite_landxml/stream/
lifecycle.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! Explicit completion, compatibility summary, and deterministic cleanup.
6
7use super::{
8    metadata, LandXmlMetadataStreamEvent, LandXmlStreamEvent, LandXmlStreamHeader,
9    LandXmlStreamSummary, LandXmlTinStreamSession, MAX_LANDXML_STREAM_DRAIN_BYTES,
10};
11use crate::{xml::error, LandXmlDiagnosticCode as Code, LandXmlError};
12
13/// Reassembles move-owned metadata cursor events for adapters that still need
14/// a complete semantic document at their compatibility boundary.
15#[derive(Default)]
16pub struct LandXmlMetadataStreamAssembler {
17    inner: metadata::MetadataReassembler,
18}
19
20impl LandXmlMetadataStreamAssembler {
21    /// Consume one event emitted through [`LandXmlStreamEvent::Metadata`].
22    pub fn push(&mut self, event: LandXmlMetadataStreamEvent) -> Result<(), LandXmlError> {
23        self.inner.push(event)
24    }
25
26    /// Return the reassembled summary after its single end event.
27    pub fn finish(self) -> Result<LandXmlStreamSummary, LandXmlError> {
28        self.inner.finish()
29    }
30}
31
32impl LandXmlTinStreamSession {
33    /// Finalize and reassemble metadata for legacy summary consumers.
34    // TODO(remove-by: #5050 worker cursor migration, owner: LandXML)
35    pub fn finish(&mut self) -> Result<LandXmlStreamSummary, LandXmlError> {
36        self.finish_cursor()?;
37        let mut reassembler = LandXmlMetadataStreamAssembler::default();
38        while self.output_pending() {
39            for event in self.drain(MAX_LANDXML_STREAM_DRAIN_BYTES)? {
40                match event {
41                    // Unit-less preserved-only sources emit their transport
42                    // header at finish. The metadata header repeats it, so a
43                    // legacy summary adapter intentionally consumes this
44                    // transport-only record without reassembling it twice.
45                    LandXmlStreamEvent::Header(_) => {}
46                    LandXmlStreamEvent::Metadata(event) => reassembler.push(*event)?,
47                    LandXmlStreamEvent::Surface(_) => {
48                        return Err(error(
49                            Code::InvalidSemantic,
50                            "summary adapter received non-metadata stream output",
51                        ));
52                    }
53                }
54            }
55        }
56        reassembler.finish()
57    }
58
59    /// Release every parser, queued event, and resumable cursor on cancellation
60    /// or abandonment. This path is deliberately idempotent for WASM hosts.
61    pub fn abort(&mut self) {
62        self.token.clear();
63        self.queue.clear();
64        self.queued_bytes = 0;
65        self.pending_surface.take();
66        self.metadata_cursor.take();
67        self.pending_input.clear();
68        self.parser.take();
69        self.plan.take();
70        self.alignment.take();
71        self.pipe.take();
72        self.closed = true;
73    }
74
75    pub fn header(&self) -> Option<LandXmlStreamHeader> {
76        self.parser
77            .as_ref()?
78            .header()
79            .map(|(version, units)| LandXmlStreamHeader { version, units })
80    }
81
82    /// Whether the caller must grant output credit with [`Self::drain`].
83    pub fn output_pending(&self) -> bool {
84        !self.queue.is_empty() || self.pending_surface.is_some() || self.metadata_cursor.is_some()
85    }
86
87    /// Exact JSON transport bytes currently retained for a credited consumer.
88    pub fn queued_bytes(&self) -> usize {
89        self.queued_bytes
90    }
91
92    /// Number of complete transport records currently retained for a consumer.
93    pub fn queued_events(&self) -> usize {
94        self.queue.len()
95    }
96}