Skip to main content

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
80/// OPC relationship discovery — finds the main model XML path within a 3MF archive.
81pub mod model_locator;
82/// OPC relationship and content type parsers.
83pub mod opc;
84/// ZIP-based `ArchiveReader` implementation using the `zip` crate.
85pub mod zip_archive;
86
87pub use model_locator::*;
88// pub use opc::*; // Clippy says unused
89pub use zip_archive::*;
90
91use crate::error::Result;
92use std::io::{Read, Seek};
93
94/// Trait for reading entries from an archive (ZIP).
95///
96/// This trait abstracts over different ZIP backend implementations, allowing the parser to work
97/// with files, in-memory buffers, async I/O, or custom storage.
98///
99/// # Requirements
100///
101/// Implementations must also implement `Read + Seek` for compatibility with the underlying ZIP library.
102///
103/// # Examples
104///
105/// ```no_run
106/// use lib3mf_core::archive::{ArchiveReader, ZipArchiver};
107/// use std::fs::File;
108///
109/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
110/// let file = File::open("model.3mf")?;
111/// let mut archive = ZipArchiver::new(file)?;
112///
113/// // Check if entry exists before reading
114/// if archive.entry_exists("3D/3dmodel.model") {
115///     let content = archive.read_entry("3D/3dmodel.model")?;
116///     println!("Model XML size: {} bytes", content.len());
117/// }
118///
119/// // List all entries
120/// for entry in archive.list_entries()? {
121///     println!("Found: {}", entry);
122/// }
123/// # Ok(())
124/// # }
125/// ```
126pub trait ArchiveReader: Read + Seek {
127    /// Read the content of an entry by name.
128    ///
129    /// # Parameters
130    ///
131    /// - `name`: Path to the entry within the archive (e.g., `"3D/3dmodel.model"`, `"Metadata/thumbnail.png"`)
132    ///
133    /// # Returns
134    ///
135    /// The binary content of the entry as a `Vec<u8>`.
136    ///
137    /// # Errors
138    ///
139    /// Returns [`Lib3mfError::Io`](crate::error::Lib3mfError::Io) if the entry doesn't exist or can't be read.
140    fn read_entry(&mut self, name: &str) -> Result<Vec<u8>>;
141
142    /// Check if an entry exists.
143    ///
144    /// # Parameters
145    ///
146    /// - `name`: Path to the entry within the archive
147    ///
148    /// # Returns
149    ///
150    /// `true` if the entry exists, `false` otherwise.
151    fn entry_exists(&mut self, name: &str) -> bool;
152
153    /// List all entries in the archive.
154    ///
155    /// # Returns
156    ///
157    /// A vector of entry paths (e.g., `["3D/3dmodel.model", "Metadata/thumbnail.png", "_rels/.rels"]`)
158    ///
159    /// # Errors
160    ///
161    /// Returns [`Lib3mfError::Io`](crate::error::Lib3mfError::Io) if the archive can't be read.
162    fn list_entries(&mut self) -> Result<Vec<String>>;
163}
164
165/// Trait for writing entries to an archive.
166///
167/// This trait abstracts over different ZIP backend implementations for creating 3MF files.
168pub trait ArchiveWriter {
169    /// Write data to an entry.
170    ///
171    /// # Parameters
172    ///
173    /// - `name`: Path for the entry within the archive (e.g., `"3D/3dmodel.model"`)
174    /// - `data`: Binary content to write
175    ///
176    /// # Errors
177    ///
178    /// Returns [`Lib3mfError::Io`](crate::error::Lib3mfError::Io) if the entry can't be written.
179    fn write_entry(&mut self, name: &str, data: &[u8]) -> Result<()>;
180}