use std::collections::BTreeMap;
use std::fmt;
use std::io::{Read, Seek, Write};
use crate::decode::{
DecodeArena, DecodeContext, DecodeMode, DecodePolicy, ErrorContext, InspectOptions,
ResourceLimit, SourceLocation, View,
};
use crate::document::CadIr;
use crate::report::DecodeReport;
use crate::report::ExportReport;
use crate::source_fidelity::SourceFidelity;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
pub trait ReadSeek: Read + Seek {}
impl<T: Read + Seek> ReadSeek for T {}
#[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, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ContainerEntry {
pub name: String,
pub role: String,
pub compression: String,
pub compressed_size: u64,
pub uncompressed_size: u64,
#[serde(default)]
pub attributes: BTreeMap<String, String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ContainerSummary {
pub format: String,
pub container_kind: String,
pub entries: Vec<ContainerEntry>,
pub notes: Vec<String>,
}
#[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) -> Self {
ir.finalize();
Self {
ir,
report,
source_fidelity: SourceFidelity::default(),
}
}
pub fn with_source_fidelity(
mut ir: CadIr,
report: DecodeReport,
mut source_fidelity: SourceFidelity,
) -> Self {
ir.finalize();
source_fidelity.finalize();
Self {
ir,
report,
source_fidelity,
}
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum CodecError {
#[error("not the expected format: {0}")]
WrongFormat(String),
#[error("malformed container: {0}")]
Malformed(String),
#[error(
"truncated input during {} at space {} offset {}",
.context.operation, .location.space.index(), .location.offset
)]
Truncated {
location: SourceLocation,
context: ErrorContext,
},
#[error(
"resource limit on {:?}: {:?} (limit {}, used {}, requested {})",
.0.dimension, .0.reason, .0.limit, .0.used, .0.additional
)]
ResourceLimit(ResourceLimit),
#[error("not implemented yet: {0}")]
NotImplemented(String),
#[error("io error: {0}")]
Io(#[from] std::io::Error),
}
impl From<crate::native::NativeConvertError> for CodecError {
fn from(error: crate::native::NativeConvertError) -> Self {
Self::Malformed(error.to_string())
}
}
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_inspection(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(result)
}
}
pub trait Encoder {
fn id(&self) -> &'static str;
fn encode(&self, ir: &CadIr, writer: &mut dyn Write) -> Result<ExportReport, CodecError>;
fn encode_with_source_fidelity(
&self,
ir: &CadIr,
source_fidelity: Option<&SourceFidelity>,
writer: &mut dyn Write,
) -> Result<ExportReport, CodecError> {
let _ = source_fidelity;
self.encode(ir, writer)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct CadirEncoder;
impl Encoder for CadirEncoder {
fn id(&self) -> &'static str {
"cadir"
}
fn encode(&self, ir: &CadIr, writer: &mut dyn Write) -> Result<ExportReport, CodecError> {
let mut json = ir
.to_canonical_json()
.map_err(|error| CodecError::Malformed(error.to_string()))?;
json.push('\n');
writer.write_all(json.as_bytes())?;
let validation = crate::validate(ir, Vec::new());
let total_entities = validation.entity_counts.values().sum();
Ok(ExportReport {
format: "cadir".into(),
entity_counts: validation.entity_counts,
total_entities,
losses: Vec::new(),
notes: Vec::new(),
})
}
}