use std::fmt;
use std::io::Write;
use std::ops::{Deref, DerefMut};
use crate::document::CadIr;
use crate::report::DecodeReport;
use crate::report::StrictConsequence;
use crate::report::{CensusBasis, EntityCensus, ExportReport, FidelityResolution, WritePath};
use crate::source_fidelity::SourceFidelity;
use cadmpeg_core::decode::{
DecodeArena, DecodeContext, DecodeMode, DecodePolicy, InspectOptions, View,
};
use cadmpeg_core::{CodecError, ContainerSummary, ReadSeek};
#[cfg(feature = "schema")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum Confidence {
No,
Low,
Medium,
High,
}
impl fmt::Display for Confidence {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::No => "no",
Self::Low => "low",
Self::Medium => "medium",
Self::High => "high",
})
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct DecodeOptions {
pub container_only: bool,
#[serde(default)]
pub policy: DecodePolicy,
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct DecodeResult {
ir: CadIr,
report: DecodeReport,
source_fidelity: SourceFidelity,
}
impl DecodeResult {
pub fn new(mut ir: CadIr, report: DecodeReport, mut source_fidelity: SourceFidelity) -> Self {
ir.finalize();
source_fidelity.finalize();
Self {
ir,
report,
source_fidelity,
}
}
pub fn ir(&self) -> &CadIr {
&self.ir
}
pub fn ir_mut(&mut self) -> impl DerefMut<Target = CadIr> + '_ {
FinalizingEdit::new(&mut self.ir, CadIr::finalize)
}
pub fn report(&self) -> &DecodeReport {
&self.report
}
pub fn report_mut(&mut self) -> &mut DecodeReport {
&mut self.report
}
pub fn source_fidelity(&self) -> &SourceFidelity {
&self.source_fidelity
}
pub fn source_fidelity_mut(&mut self) -> impl DerefMut<Target = SourceFidelity> + '_ {
FinalizingEdit::new(&mut self.source_fidelity, SourceFidelity::finalize)
}
pub fn into_parts(self) -> (CadIr, DecodeReport, SourceFidelity) {
(self.ir, self.report, self.source_fidelity)
}
}
#[must_use = "the guard keeps the DecodeResult mutably borrowed until the edit is finalized"]
struct FinalizingEdit<'a, T> {
value: &'a mut T,
finalize: fn(&mut T),
}
impl<'a, T> FinalizingEdit<'a, T> {
fn new(value: &'a mut T, finalize: fn(&mut T)) -> Self {
Self { value, finalize }
}
}
impl<T> Deref for FinalizingEdit<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.value
}
}
impl<T> DerefMut for FinalizingEdit<'_, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.value
}
}
impl<T> Drop for FinalizingEdit<'_, T> {
fn drop(&mut self) {
(self.finalize)(self.value);
}
}
pub trait CodecBackend {
fn id(&self) -> &'static str;
fn detect(&self, prefix: &[u8]) -> Confidence;
fn inspect_impl(
&self,
ctx: &DecodeContext<'_>,
root: View<'_>,
) -> Result<ContainerSummary, CodecError>;
fn decode_impl(
&self,
ctx: &DecodeContext<'_>,
root: View<'_>,
) -> Result<DecodeResult, CodecError>;
}
mod sealed {
pub trait Sealed {}
impl<C: super::CodecBackend + ?Sized> Sealed for C {}
}
pub trait Codec: CodecBackend + sealed::Sealed {
fn inspect(
&self,
reader: &mut dyn ReadSeek,
options: &InspectOptions,
) -> Result<ContainerSummary, CodecError>;
fn decode(
&self,
reader: &mut dyn ReadSeek,
options: &DecodeOptions,
) -> Result<DecodeResult, CodecError>;
}
impl<C: CodecBackend + ?Sized> Codec for C {
fn inspect(
&self,
reader: &mut dyn ReadSeek,
options: &InspectOptions,
) -> Result<ContainerSummary, CodecError> {
let arena = DecodeArena::new();
let policy = DecodePolicy {
mode: DecodeMode::Salvage,
limits: options.limits,
};
let (ctx, root) = DecodeContext::read_root(reader, &arena, &policy)?;
let result = self.inspect_impl(&ctx, root);
ctx.finish_session()?;
result
}
fn decode(
&self,
reader: &mut dyn ReadSeek,
options: &DecodeOptions,
) -> Result<DecodeResult, CodecError> {
let arena = DecodeArena::new();
let (mut ctx, root) = DecodeContext::read_root(reader, &arena, &options.policy)?;
ctx.set_container_only(options.container_only);
let result = self.decode_impl(&ctx, root);
ctx.finish_session()?;
let result = result?;
if options.policy.mode == DecodeMode::Strict && !result.report().container_only {
if let Some(loss) = result
.report()
.losses
.iter()
.find(|loss| loss.strict_consequence() == StrictConsequence::Reject)
{
return Err(CodecError::Malformed(format!(
"strict mode rejects {}: {}",
loss.code, loss.message
)));
}
}
Ok(result)
}
}
pub trait Encoder {
fn id(&self) -> &'static str;
fn plan<'a>(&self, input: EncodeInput<'a>) -> Result<ExportPlan<'a>, CodecError>;
}
#[derive(Debug, Clone, Copy)]
pub struct EncodeInput<'a> {
pub ir: &'a CadIr,
pub fidelity: Option<&'a SourceFidelity>,
}
type DeferredExport<'a> = Box<dyn FnOnce(&mut dyn Write) -> Result<(), CodecError> + 'a>;
enum ExportPayload<'a> {
Buffered(Vec<u8>),
Deferred(DeferredExport<'a>),
}
pub struct ExportPlan<'a> {
report: ExportReport,
payload: ExportPayload<'a>,
}
impl<'a> ExportPlan<'a> {
pub fn buffered(report: ExportReport, bytes: Vec<u8>) -> Self {
Self {
report,
payload: ExportPayload::Buffered(bytes),
}
}
pub fn deferred(
report: ExportReport,
write: impl FnOnce(&mut dyn Write) -> Result<(), CodecError> + 'a,
) -> Self {
Self {
report,
payload: ExportPayload::Deferred(Box::new(write)),
}
}
pub fn report(&self) -> &ExportReport {
&self.report
}
pub fn fidelity_resolution(&self) -> &FidelityResolution {
&self.report.fidelity
}
pub fn write_path(&self) -> WritePath {
self.report.write_path
}
pub fn write_to(self, writer: &mut dyn Write) -> Result<ExportReport, CodecError> {
match self.payload {
ExportPayload::Buffered(bytes) => writer.write_all(&bytes)?,
ExportPayload::Deferred(write) => write(writer)?,
}
Ok(self.report)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct CadirEncoder;
impl Encoder for CadirEncoder {
fn id(&self) -> &'static str {
"cadir"
}
fn plan<'a>(&self, input: EncodeInput<'a>) -> Result<ExportPlan<'a>, CodecError> {
let report = ExportReport {
format: "cadir".into(),
census: EntityCensus {
basis: CensusBasis::IrArenas,
counts: input.ir.census(),
},
fidelity: if input.fidelity.is_some() {
FidelityResolution::NotConsumed
} else {
FidelityResolution::NotProvided
},
write_path: WritePath::Synthesized,
losses: Vec::new(),
notes: Vec::new(),
};
Ok(ExportPlan::deferred(report, move |writer| {
serde_json::to_writer_pretty(&mut *writer, input.ir)
.map_err(|error| CodecError::Malformed(error.to_string()))?;
writer.write_all(b"\n")?;
Ok(())
}))
}
}
#[cfg(test)]
mod tests;