mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-14 06:22:24 +00:00
feat(figcraft-format): stream native document JSON
This commit is contained in:
1 parent
111b522d05
commit
30b1e426e0
2 files changed
+67
-8
No files matched your search
@@ -188,3 +188,60 @@ mod archive_tests {
|
||||
assert!(small_total.read("document.json", 100).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
/// A native archive writer that keeps the streaming document serializer out of the consumer.
|
||||
pub struct ArchiveWriter {
|
||||
inner: craft_zip::Writer,
|
||||
}
|
||||
|
||||
impl ArchiveWriter {
|
||||
pub fn new() -> Self {
|
||||
Self { inner: craft_zip::Writer::new() }
|
||||
}
|
||||
|
||||
pub fn add(&mut self, name: &str, bytes: &[u8], compression: craft_zip::Compression) -> Result<(), craft_zip::Error> {
|
||||
self.inner.add(name, bytes, compression)
|
||||
}
|
||||
|
||||
pub fn add_document_json<D: Serialize>(&mut self, name: &str, document: &D, images: &[ImageIndexEntry]) -> Result<(), ArchiveWriteError> {
|
||||
self.inner
|
||||
.add_with(name, craft_zip::Compression::Level(3), |w| write_document_json(document, images, w).map_err(std::io::Error::other))
|
||||
.map_err(ArchiveWriteError::Archive)
|
||||
}
|
||||
|
||||
pub fn finish(self) -> Result<Vec<u8>, craft_zip::Error> {
|
||||
self.inner.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ArchiveWriter {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ArchiveWriteError {
|
||||
#[error("could not serialize native document JSON: {0}")]
|
||||
Json(#[from] serde_json::Error),
|
||||
#[error("could not write native archive: {0}")]
|
||||
Archive(#[from] craft_zip::Error),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod streaming_writer_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn native_writer_streams_flat_document_into_archive() {
|
||||
let document = serde_json::json!({"name":"streamed"});
|
||||
let mut writer = ArchiveWriter::new();
|
||||
writer.add_document_json("document.json", &document, &[]).unwrap();
|
||||
let bytes = writer.finish().unwrap();
|
||||
let mut archive = Archive::open(&bytes, ArchiveLimits { max_entries: 4, max_total_bytes: 4096 }).unwrap();
|
||||
let json = archive.read("document.json", 1024).unwrap().unwrap();
|
||||
let value: Value = serde_json::from_slice(&json).unwrap();
|
||||
assert_eq!(value["name"], "streamed");
|
||||
assert_eq!(value["format"], FORMAT);
|
||||
}
|
||||
}
|
||||
+10
-8
@@ -33,22 +33,24 @@ impl Writer {
|
||||
Writer::default()
|
||||
}
|
||||
|
||||
/// Adds an entry. A name the archive already has is an error.
|
||||
/// Adds an entry from a byte slice. A name the archive already has is an error.
|
||||
pub fn add(&mut self, name: &str, data: &[u8], compression: Compression) -> Result<(), Error> {
|
||||
self.add_with(name, compression, |writer| writer.write_all(data))
|
||||
}
|
||||
|
||||
/// Adds an entry by streaming it into the compressor, so large generated entries need not
|
||||
/// first be buffered in memory. A name the archive already has is an error.
|
||||
pub fn add_with(&mut self, name: &str, compression: Compression, write: impl FnOnce(&mut dyn Write) -> std::io::Result<()>) -> Result<(), Error> {
|
||||
let (method, level) = match compression {
|
||||
Compression::Stored => (zip::CompressionMethod::Stored, None),
|
||||
Compression::Deflated => (zip::CompressionMethod::Deflated, Some(6)),
|
||||
Compression::Level(l) => (zip::CompressionMethod::Deflated, Some(i64::from(l.min(9)))),
|
||||
};
|
||||
let options = zip::write::SimpleFileOptions::default()
|
||||
.compression_method(method)
|
||||
.compression_level(level)
|
||||
.last_modified_time(zip::DateTime::default())
|
||||
// Deflate can grow incompressible data a little, so ZIP64 well before 4 GiB.
|
||||
.large_file(data.len() as u64 >= u64::from(u32::MAX) / 2);
|
||||
let options =
|
||||
zip::write::SimpleFileOptions::default().compression_method(method).compression_level(level).last_modified_time(zip::DateTime::default());
|
||||
let fail = |e: &dyn std::fmt::Display| Error::Write(format!("{name}: {e}"));
|
||||
self.zip.start_file(name, options).map_err(|e| fail(&e))?;
|
||||
self.zip.write_all(data).map_err(|e| fail(&e))
|
||||
write(&mut self.zip).map_err(|e| fail(&e))
|
||||
}
|
||||
|
||||
/// The finished archive.
|
||||
|
||||
Reference in new issue
Block a user