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}