lib3mf_core/archive/
mod.rs

1//! Archive layer for reading and writing 3MF container files.
2//!
3//! This module implements the OPC (Open Packaging Conventions) container layer that 3MF is built on.
4//! 3MF files are ZIP archives containing XML model files, textures, thumbnails, and metadata, organized
5//! according to OPC relationship and content type conventions.
6//!
7//! ## Architecture
8//!
9//! The archive layer provides:
10//!
11//! 1. **Trait-based abstraction**: [`ArchiveReader`] and [`ArchiveWriter`] traits decouple the parser
12//!    from the underlying ZIP implementation, allowing different backends (file, memory, async, etc.)
13//! 2. **OPC relationship discovery**: The [`find_model_path`] function traverses `_rels/.rels` files
14//!    to locate the main 3D model XML file within the archive.
15//! 3. **Default ZIP implementation**: [`ZipArchiver`] provides a standard file-based ZIP backend.
16//!
17//! ## Typical Usage
18//!
19//! Opening and reading a 3MF file:
20//!
21//! ```no_run
22//! use lib3mf_core::archive::{ZipArchiver, ArchiveReader, find_model_path};
23//! use std::fs::File;
24//!
25//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
26//! // Open the 3MF ZIP archive
27//! let file = File::open("model.3mf")?;
28//! let mut archiver = ZipArchiver::new(file)?;
29//!
30//! // Discover the main model XML path via OPC relationships
31//! let model_path = find_model_path(&mut archiver)?;
32//! // Typically returns "3D/3dmodel.model" or similar
33//!
34//! // Read the model XML content
35//! let model_xml = archiver.read_entry(&model_path)?;
36//!
37//! // Read attachments (textures, thumbnails, etc.)
38//! if archiver.entry_exists("Metadata/thumbnail.png") {
39//!     let thumbnail = archiver.read_entry("Metadata/thumbnail.png")?;
40//! }
41//!
42//! // List all entries
43//! let entries = archiver.list_entries()?;
44//! for entry in entries {
45//!     println!("Archive contains: {}", entry);
46//! }
47//! # Ok(())
48//! # }
49//! ```
50//!
51//! ## OPC Relationship Discovery
52//!
53//! The [`find_model_path`] function implements the OPC discovery algorithm:
54//!
55//! 1. Read `_rels/.rels` (package-level relationships)
56//! 2. Find relationship with type `http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel`
57//! 3. Extract target path (e.g., `/3D/3dmodel.model`)
58//! 4. Normalize path (remove leading `/`)
59//!
60//! This allows 3MF files to have different internal structures while remaining conformant to the spec.
61//!
62//! ## ArchiveReader Trait
63//!
64//! The [`ArchiveReader`] trait provides three core operations:
65//!
66//! - [`read_entry`](ArchiveReader::read_entry): Read file content by path
67//! - [`entry_exists`](ArchiveReader::entry_exists): Check if a path exists
68//! - [`list_entries`](ArchiveReader::list_entries): Enumerate all archive contents
69//!
70//! Implementations must also satisfy `Read + Seek` for compatibility with the ZIP crate.
71//!
72//! ## ArchiveWriter Trait
73//!
74//! The [`ArchiveWriter`] trait provides a single operation:
75//!
76//! - [`write_entry`](ArchiveWriter::write_entry): Write data to a path in the archive
77//!
78//! Implementations handle compression, content type registration, and relationship generation.
79
80pub mod model_locator;
81pub mod opc;
82pub mod zip_archive;
83
84pub use model_locator::*;
85// pub use opc::*; // Clippy says unused
86pub use zip_archive::*;
87
88use crate::error::Result;
89use std::io::{Read, Seek};
90
91/// Trait for reading entries from an archive (ZIP).
92///
93/// This trait abstracts over different ZIP backend implementations, allowing the parser to work
94/// with files, in-memory buffers, async I/O, or custom storage.
95///
96/// # Requirements
97///
98/// Implementations must also implement `Read + Seek` for compatibility with the underlying ZIP library.
99///
100/// # Examples
101///
102/// ```no_run
103/// use lib3mf_core::archive::{ArchiveReader, ZipArchiver};
104/// use std::fs::File;
105///
106/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
107/// let file = File::open("model.3mf")?;
108/// let mut archive = ZipArchiver::new(file)?;
109///
110/// // Check if entry exists before reading
111/// if archive.entry_exists("3D/3dmodel.model") {
112///     let content = archive.read_entry("3D/3dmodel.model")?;
113///     println!("Model XML size: {} bytes", content.len());
114/// }
115///
116/// // List all entries
117/// for entry in archive.list_entries()? {
118///     println!("Found: {}", entry);
119/// }
120/// # Ok(())
121/// # }
122/// ```
123pub trait ArchiveReader: Read + Seek {
124    /// Read the content of an entry by name.
125    ///
126    /// # Parameters
127    ///
128    /// - `name`: Path to the entry within the archive (e.g., `"3D/3dmodel.model"`, `"Metadata/thumbnail.png"`)
129    ///
130    /// # Returns
131    ///
132    /// The binary content of the entry as a `Vec<u8>`.
133    ///
134    /// # Errors
135    ///
136    /// Returns [`Lib3mfError::Io`](crate::error::Lib3mfError::Io) if the entry doesn't exist or can't be read.
137    fn read_entry(&mut self, name: &str) -> Result<Vec<u8>>;
138
139    /// Check if an entry exists.
140    ///
141    /// # Parameters
142    ///
143    /// - `name`: Path to the entry within the archive
144    ///
145    /// # Returns
146    ///
147    /// `true` if the entry exists, `false` otherwise.
148    fn entry_exists(&mut self, name: &str) -> bool;
149
150    /// List all entries in the archive.
151    ///
152    /// # Returns
153    ///
154    /// A vector of entry paths (e.g., `["3D/3dmodel.model", "Metadata/thumbnail.png", "_rels/.rels"]`)
155    ///
156    /// # Errors
157    ///
158    /// Returns [`Lib3mfError::Io`](crate::error::Lib3mfError::Io) if the archive can't be read.
159    fn list_entries(&mut self) -> Result<Vec<String>>;
160}
161
162/// Trait for writing entries to an archive.
163///
164/// This trait abstracts over different ZIP backend implementations for creating 3MF files.
165pub trait ArchiveWriter {
166    /// Write data to an entry.
167    ///
168    /// # Parameters
169    ///
170    /// - `name`: Path for the entry within the archive (e.g., `"3D/3dmodel.model"`)
171    /// - `data`: Binary content to write
172    ///
173    /// # Errors
174    ///
175    /// Returns [`Lib3mfError::Io`](crate::error::Lib3mfError::Io) if the entry can't be written.
176    fn write_entry(&mut self, name: &str, data: &[u8]) -> Result<()>;
177}