Skip to main content

uqa_analysis/analyzer/
diagnostics.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Serialize complete analysis diagnostics while retaining analysis and output reservations.
8
9use std::io::{self, Write};
10
11use serde::Serialize;
12use uqa_core::memory::{Budgeted, BudgetedVec, MemoryBudget};
13
14use crate::{AnalysisError, AnalysisResult, AnalyzedText, AnalyzerFingerprint, CompiledAnalyzer};
15
16#[derive(Serialize)]
17struct Diagnostic<'a> {
18    #[serde(flatten)]
19    analysis: &'a AnalyzedText,
20    analyzer_fingerprint: &'a AnalyzerFingerprint,
21}
22
23impl CompiledAnalyzer {
24    /// Analyze and encode the complete token graph, source end state, and immutable revision.
25    ///
26    /// Analysis and JSON output share the supplied allowance. The token stream remains reserved until encoding finishes, and cancellation is checked while writing. The returned string retains its output reservation.
27    pub fn analyze_diagnostic_budgeted(
28        &self,
29        text: &str,
30        budget: &MemoryBudget,
31        mut poll: impl FnMut() -> AnalysisResult<()>,
32    ) -> AnalysisResult<Budgeted<String>> {
33        let analysis = self.analyze_tokens_budgeted(text, budget, &mut poll)?;
34        let diagnostic = Diagnostic {
35            analysis: &analysis,
36            analyzer_fingerprint: &self.descriptor().fingerprint(),
37        };
38        let mut writer = DiagnosticWriter {
39            bytes: BudgetedVec::new(budget),
40            poll: &mut poll,
41            failure: None,
42        };
43        let result = serde_json::to_writer(&mut writer, &diagnostic);
44        if let Some(error) = writer.failure {
45            return Err(error);
46        }
47        result?;
48        (writer.poll)()?;
49        drop(analysis);
50        let (bytes, memory) = writer.bytes.into_parts();
51        let json = String::from_utf8(bytes).expect("JSON serialization emits valid UTF-8");
52        Ok(Budgeted::new(json, memory))
53    }
54}
55
56struct DiagnosticWriter<'a> {
57    bytes: BudgetedVec<u8>,
58    poll: &'a mut dyn FnMut() -> AnalysisResult<()>,
59    failure: Option<AnalysisError>,
60}
61
62impl Write for DiagnosticWriter<'_> {
63    fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
64        let result = (self.poll)().and_then(|()| {
65            self.bytes.extend_from_slice(bytes)?;
66            Ok(())
67        });
68        if let Err(error) = result {
69            self.failure = Some(error);
70            return Err(io::Error::other("analysis diagnostic encoding interrupted"));
71        }
72        Ok(bytes.len())
73    }
74
75    fn flush(&mut self) -> io::Result<()> {
76        Ok(())
77    }
78}
79
80#[cfg(test)]
81mod tests;