1use std::fmt;
16use std::io::Write;
17use std::ops::{Deref, DerefMut};
18
19use crate::document::CadIr;
20use crate::report::DecodeReport;
21use crate::report::StrictConsequence;
22use crate::report::{CensusBasis, EntityCensus, ExportReport, FidelityResolution, WritePath};
23use crate::source_fidelity::SourceFidelity;
24use cadmpeg_core::decode::{
25 DecodeArena, DecodeContext, DecodeMode, DecodePolicy, InspectOptions, View,
26};
27use cadmpeg_core::{CodecError, ContainerSummary, ReadSeek};
28#[cfg(feature = "schema")]
29use schemars::JsonSchema;
30use serde::{Deserialize, Serialize};
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
34#[cfg_attr(feature = "schema", derive(JsonSchema))]
35#[serde(rename_all = "snake_case")]
36pub enum Confidence {
37 No,
39 Low,
41 Medium,
43 High,
45}
46
47impl fmt::Display for Confidence {
48 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49 f.write_str(match self {
50 Self::No => "no",
51 Self::Low => "low",
52 Self::Medium => "medium",
53 Self::High => "high",
54 })
55 }
56}
57
58#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
60#[cfg_attr(feature = "schema", derive(JsonSchema))]
61pub struct DecodeOptions {
62 pub container_only: bool,
64 #[serde(default)]
66 pub policy: DecodePolicy,
67}
68
69#[derive(Debug, Clone, PartialEq)]
78#[non_exhaustive]
79pub struct DecodeResult {
80 ir: CadIr,
81 report: DecodeReport,
82 source_fidelity: SourceFidelity,
83}
84
85impl DecodeResult {
86 pub fn new(mut ir: CadIr, report: DecodeReport, mut source_fidelity: SourceFidelity) -> Self {
88 ir.finalize();
89 source_fidelity.finalize();
90 Self {
91 ir,
92 report,
93 source_fidelity,
94 }
95 }
96
97 pub fn ir(&self) -> &CadIr {
99 &self.ir
100 }
101
102 pub fn ir_mut(&mut self) -> impl DerefMut<Target = CadIr> + '_ {
104 FinalizingEdit::new(&mut self.ir, CadIr::finalize)
105 }
106
107 pub fn report(&self) -> &DecodeReport {
109 &self.report
110 }
111
112 pub fn report_mut(&mut self) -> &mut DecodeReport {
114 &mut self.report
115 }
116
117 pub fn source_fidelity(&self) -> &SourceFidelity {
119 &self.source_fidelity
120 }
121
122 pub fn source_fidelity_mut(&mut self) -> impl DerefMut<Target = SourceFidelity> + '_ {
124 FinalizingEdit::new(&mut self.source_fidelity, SourceFidelity::finalize)
125 }
126
127 pub fn into_parts(self) -> (CadIr, DecodeReport, SourceFidelity) {
129 (self.ir, self.report, self.source_fidelity)
130 }
131}
132
133#[must_use = "the guard keeps the DecodeResult mutably borrowed until the edit is finalized"]
134struct FinalizingEdit<'a, T> {
135 value: &'a mut T,
136 finalize: fn(&mut T),
137}
138
139impl<'a, T> FinalizingEdit<'a, T> {
140 fn new(value: &'a mut T, finalize: fn(&mut T)) -> Self {
141 Self { value, finalize }
142 }
143}
144
145impl<T> Deref for FinalizingEdit<'_, T> {
146 type Target = T;
147
148 fn deref(&self) -> &Self::Target {
149 self.value
150 }
151}
152
153impl<T> DerefMut for FinalizingEdit<'_, T> {
154 fn deref_mut(&mut self) -> &mut Self::Target {
155 self.value
156 }
157}
158
159impl<T> Drop for FinalizingEdit<'_, T> {
160 fn drop(&mut self) {
161 (self.finalize)(self.value);
162 }
163}
164
165pub trait CodecBackend {
167 fn id(&self) -> &'static str;
169
170 fn detect(&self, prefix: &[u8]) -> Confidence;
172
173 fn inspect_impl(
180 &self,
181 ctx: &DecodeContext<'_>,
182 root: View<'_>,
183 ) -> Result<ContainerSummary, CodecError>;
184
185 fn decode_impl(
192 &self,
193 ctx: &DecodeContext<'_>,
194 root: View<'_>,
195 ) -> Result<DecodeResult, CodecError>;
196}
197
198mod sealed {
199 pub trait Sealed {}
201 impl<C: super::CodecBackend + ?Sized> Sealed for C {}
202}
203
204pub trait Codec: CodecBackend + sealed::Sealed {
231 fn inspect(
233 &self,
234 reader: &mut dyn ReadSeek,
235 options: &InspectOptions,
236 ) -> Result<ContainerSummary, CodecError>;
237
238 fn decode(
250 &self,
251 reader: &mut dyn ReadSeek,
252 options: &DecodeOptions,
253 ) -> Result<DecodeResult, CodecError>;
254}
255
256impl<C: CodecBackend + ?Sized> Codec for C {
257 fn inspect(
258 &self,
259 reader: &mut dyn ReadSeek,
260 options: &InspectOptions,
261 ) -> Result<ContainerSummary, CodecError> {
262 let arena = DecodeArena::new();
263 let policy = DecodePolicy {
264 mode: DecodeMode::Salvage,
265 limits: options.limits,
266 };
267 let (ctx, root) = DecodeContext::read_root(reader, &arena, &policy)?;
268 let result = self.inspect_impl(&ctx, root);
269 ctx.finish_session()?;
270 result
271 }
272
273 fn decode(
274 &self,
275 reader: &mut dyn ReadSeek,
276 options: &DecodeOptions,
277 ) -> Result<DecodeResult, CodecError> {
278 let arena = DecodeArena::new();
279 let (mut ctx, root) = DecodeContext::read_root(reader, &arena, &options.policy)?;
280 ctx.set_container_only(options.container_only);
281 let result = self.decode_impl(&ctx, root);
282 ctx.finish_session()?;
283 let mut result = result?;
284 result.report_mut().container_only = options.container_only;
285 if options.policy.mode == DecodeMode::Strict && !options.container_only {
286 if let Some(loss) = result
287 .report()
288 .losses
289 .iter()
290 .find(|loss| loss.strict_consequence() == StrictConsequence::Reject)
291 {
292 return Err(CodecError::StrictRefusal {
293 loss_code: loss.code.to_string(),
294 message: loss.message.clone(),
295 });
296 }
297 }
298 Ok(result)
299 }
300}
301
302pub trait Encoder {
304 fn id(&self) -> &'static str;
306
307 fn plan<'a>(&self, input: EncodeInput<'a>) -> Result<ExportPlan<'a>, CodecError>;
309}
310
311#[derive(Debug, Clone, Copy)]
313pub struct EncodeInput<'a> {
314 pub ir: &'a CadIr,
316 pub fidelity: Option<&'a SourceFidelity>,
318}
319
320type DeferredExport<'a> = Box<dyn FnOnce(&mut dyn Write) -> Result<(), CodecError> + 'a>;
321
322enum ExportPayload<'a> {
323 Buffered(Vec<u8>),
324 Deferred(DeferredExport<'a>),
325}
326
327pub struct ExportPlan<'a> {
329 report: ExportReport,
330 payload: ExportPayload<'a>,
331}
332
333impl<'a> ExportPlan<'a> {
334 pub fn buffered(report: ExportReport, bytes: Vec<u8>) -> Self {
338 Self {
339 report,
340 payload: ExportPayload::Buffered(bytes),
341 }
342 }
343
344 pub fn deferred(
348 report: ExportReport,
349 write: impl FnOnce(&mut dyn Write) -> Result<(), CodecError> + 'a,
350 ) -> Self {
351 Self {
352 report,
353 payload: ExportPayload::Deferred(Box::new(write)),
354 }
355 }
356
357 pub fn report(&self) -> &ExportReport {
359 &self.report
360 }
361
362 pub fn fidelity_resolution(&self) -> &FidelityResolution {
364 &self.report.fidelity
365 }
366
367 pub fn write_path(&self) -> WritePath {
369 self.report.write_path
370 }
371
372 pub fn write_to(self, writer: &mut dyn Write) -> Result<ExportReport, CodecError> {
374 match self.payload {
375 ExportPayload::Buffered(bytes) => writer.write_all(&bytes)?,
376 ExportPayload::Deferred(write) => write(writer)?,
377 }
378 Ok(self.report)
379 }
380}
381
382#[derive(Debug, Clone, Copy, Default)]
384pub struct CadirEncoder;
385
386impl Encoder for CadirEncoder {
387 fn id(&self) -> &'static str {
388 "cadir"
389 }
390
391 fn plan<'a>(&self, input: EncodeInput<'a>) -> Result<ExportPlan<'a>, CodecError> {
392 let report = ExportReport {
393 format: "cadir".into(),
394 census: EntityCensus {
395 basis: CensusBasis::IrArenas,
396 counts: input.ir.census(),
397 },
398 fidelity: if input.fidelity.is_some() {
399 FidelityResolution::NotConsumed
400 } else {
401 FidelityResolution::NotProvided
402 },
403 write_path: WritePath::Synthesized,
406 losses: Vec::new(),
407 notes: Vec::new(),
408 };
409 Ok(ExportPlan::deferred(report, move |writer| {
410 serde_json::to_writer_pretty(&mut *writer, input.ir)
411 .map_err(|error| CodecError::Malformed(error.to_string()))?;
412 writer.write_all(b"\n")?;
413 Ok(())
414 }))
415 }
416}
417
418#[cfg(test)]
419mod tests;