use std::fmt;
use std::io::Write;
use crate::document::CadIr;
use crate::report::DecodeReport;
use crate::report::StrictConsequence;
use crate::report::{CensusBasis, EntityCensus, ExportReport, FidelityResolution};
use crate::source_fidelity::SourceFidelity;
use cadmpeg_codec_core::decode::{
DecodeArena, DecodeContext, DecodeMode, DecodePolicy, InspectOptions, View,
};
use cadmpeg_codec_core::{CodecError, ContainerSummary, ReadSeek};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, 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, JsonSchema)]
pub struct DecodeOptions {
pub container_only: bool,
#[serde(default)]
pub policy: DecodePolicy,
}
#[derive(Debug, Clone, PartialEq)]
pub struct DecodeResult {
pub ir: CadIr,
pub report: DecodeReport,
pub 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 trait Codec {
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::Codec + ?Sized> Sealed for C {}
}
pub trait CodecEntry: Codec + 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: Codec + ?Sized> CodecEntry 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, fidelity: FidelityResolution, bytes: Vec<u8>) -> Self {
Self {
report: ExportReport { fidelity, ..report },
payload: ExportPayload::Buffered(bytes),
}
}
pub fn deferred(
report: ExportReport,
fidelity: FidelityResolution,
write: impl FnOnce(&mut dyn Write) -> Result<(), CodecError> + 'a,
) -> Self {
Self {
report: ExportReport { fidelity, ..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_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 validation = crate::validate(input.ir, Vec::new());
let report = ExportReport {
format: "cadir".into(),
census: EntityCensus {
basis: CensusBasis::IrArenas,
counts: validation.entity_counts,
},
fidelity: FidelityResolution::NotProvided,
losses: Vec::new(),
notes: Vec::new(),
};
let fidelity = if input.fidelity.is_some() {
FidelityResolution::NotConsumed
} else {
FidelityResolution::NotProvided
};
Ok(ExportPlan::deferred(report, fidelity, move |writer| {
let mut json = input
.ir
.to_canonical_json()
.map_err(|error| CodecError::Malformed(error.to_string()))?;
json.push('\n');
writer.write_all(json.as_bytes())?;
Ok(())
}))
}
}