use std::fmt;
use self::loss::DecodeLossCode;
use crate::document::CadIr;
use crate::report::{
check::Finding,
decode::{Coverage, DecodeReport, DecodeTransfer, TransferLedger},
loss::{LossNote as DecodeLoss, StrictConsequence},
};
use crate::source_fidelity::SourceFidelity;
use crate::ContainerSummary;
use cadmpeg_core::decode::{
DecodeArena, DecodeContext, DecodeDepth, DecodeMode, DecodePolicy, InspectOptions, View,
};
use cadmpeg_core::dialect::FormatIdentity;
use cadmpeg_core::{CodecError, ReadSeek};
#[cfg(feature = "schema")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct FormatId(&'static str);
impl FormatId {
#[must_use]
pub const fn new(namespace: &'static str) -> Self {
Self(namespace)
}
#[must_use]
pub const fn as_str(self) -> &'static str {
self.0
}
}
impl fmt::Display for FormatId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad(self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
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))]
#[serde(deny_unknown_fields)]
pub struct DecodeOptions {
#[serde(default)]
pub depth: DecodeDepth,
#[serde(default)]
pub policy: DecodePolicy,
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct DecodeResult {
ir: CadIr,
report: DecodeReport,
source_fidelity: SourceFidelity,
}
#[derive(Debug)]
pub struct StrictDecodeRejection {
report: Box<DecodeReport>,
loss_index: usize,
}
#[derive(Debug, Clone, Copy)]
struct RejectingLossIndex(usize);
impl StrictDecodeRejection {
fn new(report: DecodeReport, loss_index: RejectingLossIndex) -> Self {
Self {
report: Box::new(report),
loss_index: loss_index.0,
}
}
#[must_use]
pub fn loss(&self) -> &DecodeLoss {
&self.report.losses[self.loss_index]
}
#[must_use]
pub fn report(&self) -> &DecodeReport {
&self.report
}
}
impl fmt::Display for StrictDecodeRejection {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let loss = self.loss();
write!(f, "{}: {}", loss.code, loss.message)
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum DecodeFailure {
#[error(transparent)]
Codec(#[from] CodecError),
#[error("strict mode rejects {rejection}")]
StrictRejected {
rejection: StrictDecodeRejection,
},
}
#[derive(Debug, Clone, PartialEq)]
pub struct Decoded {
pub ir: CadIr,
pub body: DecodeBody,
pub source_fidelity: SourceFidelity,
}
#[derive(Debug, Clone, PartialEq)]
pub struct DecodeBody {
pub transfer: DecodeTransfer,
pub coverage: Coverage,
pub losses: Vec<DecodeLoss>,
pub notes: Vec<String>,
pub transfer_ledger: TransferLedger,
}
impl DecodeBody {
#[must_use]
pub fn new(transfer: DecodeTransfer) -> Self {
Self {
transfer,
coverage: Coverage::default(),
losses: Vec::new(),
notes: Vec::new(),
transfer_ledger: TransferLedger::default(),
}
}
}
impl DecodeResult {
fn new(decoded: Decoded, format: FormatId, requested: DecodeDepth) -> Result<Self, CodecError> {
let Decoded {
mut ir,
mut body,
source_fidelity,
} = decoded;
let delivered = body.transfer.depth();
ir.set_decode_depth(delivered);
ir.check_decode_depth().map_err(|error| {
CodecError::malformed(format_args!("codec {format:?} decoded {error}"))
})?;
if delivered < requested {
body.losses.push(
DecodeLossCode::DepthNotDelivered { delivered }.note(format!(
"requested {requested} depth but the {format} decoder delivered \
{delivered} depth, so the records only {requested} depth carries are absent"
)),
);
}
let classification = match ir.source.as_ref() {
Some(source) => match source.classification() {
FormatIdentity::Classified { dialects } => {
FormatIdentity::classified(dialects.clone())
}
FormatIdentity::Unclassified { format } => {
FormatIdentity::unclassified(format.to_owned())
}
},
None => FormatIdentity::unclassified(format.as_str().to_owned()),
};
ir.finalize();
Ok(Self {
ir,
report: DecodeReport::from_body(classification, body),
source_fidelity,
})
}
pub fn ir(&self) -> &CadIr {
&self.ir
}
pub fn report(&self) -> &DecodeReport {
&self.report
}
pub fn into_parts(self) -> (CadIr, DecodeReport, SourceFidelity) {
(self.ir, self.report, self.source_fidelity)
}
}
pub trait CodecBackend {
const FORMAT: FormatId;
fn validate_native(_ctx: &DecodeContext<'_>, _ir: &CadIr) -> Result<Vec<Finding>, CodecError> {
Ok(Vec::new())
}
fn detect_impl(
&self,
ctx: &DecodeContext<'_>,
prefix: View<'_>,
) -> Result<Confidence, CodecError>;
fn inspect_impl(
&self,
ctx: &DecodeContext<'_>,
root: View<'_>,
) -> Result<ContainerSummary, CodecError>;
fn decode_impl(&self, ctx: &DecodeContext<'_>, root: View<'_>) -> Result<Decoded, CodecError>;
}
mod sealed {
pub trait Sealed {}
impl<C: super::CodecBackend + ?Sized> Sealed for C {}
}
pub trait Codec: sealed::Sealed {
fn id(&self) -> FormatId;
fn detect(&self, ctx: &DecodeContext<'_>, prefix: View<'_>) -> Result<Confidence, CodecError>;
fn validate_native(
&self,
ctx: &DecodeContext<'_>,
ir: &CadIr,
) -> Result<Vec<Finding>, CodecError>;
fn inspect(
&self,
reader: &mut dyn ReadSeek,
options: &InspectOptions,
) -> Result<ContainerSummary, CodecError>;
fn decode(
&self,
reader: &mut dyn ReadSeek,
options: &DecodeOptions,
) -> Result<DecodeResult, DecodeFailure>;
fn decode_with_context(
&self,
ctx: &DecodeContext<'_>,
root: View<'_>,
) -> Result<DecodeResult, DecodeFailure>;
}
impl<C: CodecBackend + ?Sized> Codec for C {
fn id(&self) -> FormatId {
C::FORMAT
}
fn detect(&self, ctx: &DecodeContext<'_>, prefix: View<'_>) -> Result<Confidence, CodecError> {
self.detect_impl(ctx, prefix)
}
fn validate_native(
&self,
ctx: &DecodeContext<'_>,
ir: &CadIr,
) -> Result<Vec<Finding>, CodecError> {
C::validate_native(ctx, ir)
}
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, DecodeDepth::Full)?;
let result = self.inspect_impl(&ctx, root);
ctx.finish_session()?;
let result = result?;
if result.format() != C::FORMAT.as_str() {
return Err(CodecError::WrongFormat(format!(
"codec {:?} inspected a {:?} container",
C::FORMAT.as_str(),
result.format()
)));
}
Ok(result)
}
fn decode(
&self,
reader: &mut dyn ReadSeek,
options: &DecodeOptions,
) -> Result<DecodeResult, DecodeFailure> {
let arena = DecodeArena::new();
let (ctx, root) = DecodeContext::read_root(reader, &arena, &options.policy, options.depth)?;
let result = self.decode_with_context(&ctx, root);
ctx.finish_session()?;
result
}
fn decode_with_context(
&self,
ctx: &DecodeContext<'_>,
root: View<'_>,
) -> Result<DecodeResult, DecodeFailure> {
let decoded = self.decode_impl(ctx, root)?;
let result = DecodeResult::new(decoded, C::FORMAT, ctx.depth())?;
if result.report().format() != C::FORMAT.as_str() {
return Err(CodecError::WrongFormat(format!(
"codec {:?} decoded a {:?} document",
C::FORMAT.as_str(),
result.report().format()
))
.into());
}
crate::validate::evaluation_cycles::admit_evaluation_cycles(ctx, result.ir())?;
let strict_loss_index = if ctx.policy().mode == DecodeMode::Strict && !ctx.container_only()
{
result
.report()
.losses
.iter()
.position(|loss| loss.strict_consequence() == StrictConsequence::Reject)
.map(RejectingLossIndex)
} else {
None
};
if let Some(loss_index) = strict_loss_index {
let (_, report, _) = result.into_parts();
let rejection = StrictDecodeRejection::new(report, loss_index);
return Err(DecodeFailure::StrictRejected { rejection });
}
Ok(result)
}
}
mod loss;
pub mod write;
#[cfg(test)]
mod tests;