From 1c0b9a7b9b7be7e8b6e4eb3b3adbc58a49a545fb Mon Sep 17 00:00:00 2001 From: josoriom Date: Wed, 16 Sep 2026 10:07:55 +0800 Subject: [PATCH 1/3] feat: replace ruzstd by osmo --- Cargo.lock | 22 ++++----------- Cargo.toml | 2 +- crates/parser/Cargo.toml | 2 +- .../src/ion/decoder/utilities/common.rs | 19 ++++--------- .../src/ion/encoder/utilities/block_writer.rs | 28 +++++++------------ .../encoder/utilities/meta_collector/mod.rs | 9 ++++-- 6 files changed, 30 insertions(+), 52 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e2c5cef4..f776c8c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -431,10 +431,10 @@ dependencies = [ "memmap2", "miniz_oxide", "once_cell", + "osmo", "proptest", "quick-xml", "rayon", - "ruzstd", "serde", "serde_json", "sha1", @@ -544,6 +544,11 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "osmo" +version = "0.1.0" +source = "git+https://github.com/josoriom/osmo.git?rev=bccc1d16805ac446e2c32294d69a0090a2b85181#bccc1d16805ac446e2c32294d69a0090a2b85181" + [[package]] name = "pkg-config" version = "0.3.33" @@ -765,15 +770,6 @@ dependencies = [ "wait-timeout", ] -[[package]] -name = "ruzstd" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7c1c839d570d835527c9a5e4db7cb2198683a988cb9d7293fc8674e6bd58fc8" -dependencies = [ - "twox-hash", -] - [[package]] name = "serde" version = "1.0.228" @@ -894,12 +890,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" -[[package]] -name = "twox-hash" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" - [[package]] name = "typenum" version = "1.20.1" diff --git a/Cargo.toml b/Cargo.toml index ed221cf2..f3af6902 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ serde = { version = "1", features = ["derive"] } serde_json = "1.0" clap = "4.6.1" zstd = "0.13.3" -ruzstd = "0.8.3" +osmo = { git = "https://github.com/josoriom/osmo.git", rev = "bccc1d16805ac446e2c32294d69a0090a2b85181", default-features = false } regex = "1.12.4" rayon = "1.12.0" itoa = "1.0" diff --git a/crates/parser/Cargo.toml b/crates/parser/Cargo.toml index bfbde113..39c4136b 100644 --- a/crates/parser/Cargo.toml +++ b/crates/parser/Cargo.toml @@ -23,7 +23,7 @@ rayon = { workspace = true } memmap2 = "0.9" [target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dependencies] -ruzstd = { workspace = true } +osmo = { workspace = true, features = ["std"] } [dev-dependencies] proptest = "1" diff --git a/crates/parser/src/ion/decoder/utilities/common.rs b/crates/parser/src/ion/decoder/utilities/common.rs index 75205f61..f9756919 100644 --- a/crates/parser/src/ion/decoder/utilities/common.rs +++ b/crates/parser/src/ion/decoder/utilities/common.rs @@ -1,6 +1,3 @@ -#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] -use std::io::Read; - #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))] use zstd::zstd_safe; @@ -121,19 +118,13 @@ pub(crate) fn decompress_zstd( budget.validate(comp.len(), expected)?; - let mut decoder = ruzstd::decoding::StreamingDecoder::new(comp) + let mut out = vec![0u8; expected]; + let mut workspace = osmo::DecodeWorkspace::new_boxed(); + let actual = osmo::decompress(comp, &mut out, &mut workspace) .map_err(|err| IonError::from(format!("zstd decode failed: {err:?}")))?; - let mut out = Vec::with_capacity(expected); - decoder - .read_to_end(&mut out) - .map_err(|err| IonError::from(format!("zstd decode failed: {err}")))?; - if out.len() != expected { - return Err(format!( - "zstd: bad decoded size (got={}, expected={expected})", - out.len() - ) - .into()); + if actual != expected { + return Err(format!("zstd: bad decoded size (got={actual}, expected={expected})").into()); } Ok(out) diff --git a/crates/parser/src/ion/encoder/utilities/block_writer.rs b/crates/parser/src/ion/encoder/utilities/block_writer.rs index bc067d94..f2455947 100644 --- a/crates/parser/src/ion/encoder/utilities/block_writer.rs +++ b/crates/parser/src/ion/encoder/utilities/block_writer.rs @@ -57,9 +57,7 @@ pub(crate) struct DefaultCompressor { } #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] -pub(crate) struct DefaultCompressor { - level: ruzstd::encoding::CompressionLevel, -} +pub(crate) struct DefaultCompressor; #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))] impl DefaultCompressor { @@ -74,19 +72,8 @@ impl DefaultCompressor { #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] impl DefaultCompressor { - pub(crate) fn new(compression_level: i32) -> IonResult { - Ok(Self { - level: get_ruzstd_level(compression_level), - }) - } -} - -#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] -pub(crate) fn get_ruzstd_level(compression_level: i32) -> ruzstd::encoding::CompressionLevel { - if compression_level == 0 { - ruzstd::encoding::CompressionLevel::Uncompressed - } else { - ruzstd::encoding::CompressionLevel::Fastest + pub(crate) fn new(_compression_level: i32) -> IonResult { + Ok(Self) } } @@ -112,9 +99,14 @@ impl BlockCompressor for DefaultCompressor { #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] impl BlockCompressor for DefaultCompressor { fn compress(&mut self, input: &[u8], output: &mut Vec) -> IonResult { + let options = osmo::CompressOptions::zstd(); output.clear(); - ruzstd::encoding::compress(input, &mut *output, self.level); - Ok(output.len()) + output.resize(osmo::get_max_compressed_size(input.len(), &options), 0); + let mut workspace = osmo::EncodeWorkspace::new_boxed(); + let written = osmo::compress(input, output, &options, &mut workspace) + .map_err(|err| IonError::from(format!("zstd encode failed: {err:?}")))?; + output.truncate(written); + Ok(written) } fn shuffle_bytes_into(&self, input: &[u8], output: &mut [u8], element_stride: usize) { diff --git a/crates/parser/src/ion/encoder/utilities/meta_collector/mod.rs b/crates/parser/src/ion/encoder/utilities/meta_collector/mod.rs index 140931e5..116a3a26 100644 --- a/crates/parser/src/ion/encoder/utilities/meta_collector/mod.rs +++ b/crates/parser/src/ion/encoder/utilities/meta_collector/mod.rs @@ -628,6 +628,11 @@ pub(crate) fn compress_bytes_if_enabled(bytes: Vec, level: u8) -> Vec { if level == 0 { return bytes; } - let ruzstd_level = super::block_writer::get_ruzstd_level(level as i32); - ruzstd::encoding::compress_to_vec(bytes.as_slice(), ruzstd_level) + let options = osmo::CompressOptions::zstd(); + let mut out = vec![0u8; osmo::get_max_compressed_size(bytes.len(), &options)]; + let mut workspace = osmo::EncodeWorkspace::new_boxed(); + let written = osmo::compress(&bytes, &mut out, &options, &mut workspace) + .expect("zstd compression failed"); + out.truncate(written); + out } From 86afbc37e05a16d2cc85d6039d53a8f0ba58b99e Mon Sep 17 00:00:00 2001 From: josoriom Date: Thu, 17 Sep 2026 16:11:45 +0800 Subject: [PATCH 2/3] release: use cosmoz for zstd + release ionic-rs 0.1.2 --- Cargo.lock | 72 ++--------- Cargo.toml | 9 +- README.md | 9 +- crates/cli/Cargo.toml | 7 +- crates/cli/src/README.MD | 4 +- crates/cli/src/main.rs | 6 +- crates/cli/src/utilities/header.rs | 71 +++++++++-- crates/parser/Cargo.toml | 23 ++-- crates/parser/LICENSE | 1 + crates/parser/src/convert.rs | 2 +- crates/parser/src/ion/decoder/decode/mod.rs | 118 +++++++++--------- crates/parser/src/ion/decoder/decode/tests.rs | 3 +- .../src/ion/decoder/utilities/common.rs | 50 ++------ .../ion/decoder/utilities/parse_metadata.rs | 12 +- crates/parser/src/ion/encoder/encode.rs | 11 +- .../src/ion/encoder/utilities/block_writer.rs | 68 ++++------ .../encoder/utilities/meta_collector/mod.rs | 28 ++--- .../encoder/utilities/meta_collector/tests.rs | 12 +- .../src/ion/encoder/utilities/output.rs | 30 +++-- crates/parser/tests/api_contract.rs | 4 +- crates/parser/tests/convert.rs | 2 +- crates/parser/tests/ion_writer_differ.rs | 3 +- crates/parser/tests/mz_order_rejection.rs | 4 +- crates/xtask/Cargo.toml | 2 +- 24 files changed, 270 insertions(+), 281 deletions(-) create mode 120000 crates/parser/LICENSE diff --git a/Cargo.lock b/Cargo.lock index f776c8c1..a4f48151 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -154,8 +154,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38" dependencies = [ "find-msvc-tools", - "jobserver", - "libc", "shlex", ] @@ -207,12 +205,13 @@ checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "cli" -version = "0.1.1" +version = "0.1.2" dependencies = [ "assert_cmd", "clap", + "cosmoz", "crc32fast", - "ionic", + "ionic-rs", "memmap2", "mimalloc", "predicates", @@ -221,7 +220,6 @@ dependencies = [ "serde", "serde_json", "tempfile", - "zstd", ] [[package]] @@ -236,6 +234,12 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" +[[package]] +name = "cosmoz" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbb1a774049ae9d1e1e956840ecee8b801047cd803620a308950bb3e1cccb76" + [[package]] name = "cpufeatures" version = "0.3.0" @@ -420,25 +424,24 @@ dependencies = [ ] [[package]] -name = "ionic" -version = "0.1.1" +name = "ionic-rs" +version = "0.1.2" dependencies = [ "base64", "bytemuck", + "cosmoz", "crc32fast", "hashbrown", "itoa", "memmap2", "miniz_oxide", "once_cell", - "osmo", "proptest", "quick-xml", "rayon", "serde", "serde_json", "sha1", - "zstd", ] [[package]] @@ -453,16 +456,6 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" -[[package]] -name = "jobserver" -version = "0.1.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" -dependencies = [ - "getrandom 0.4.3", - "libc", -] - [[package]] name = "libc" version = "0.2.186" @@ -544,17 +537,6 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" -[[package]] -name = "osmo" -version = "0.1.0" -source = "git+https://github.com/josoriom/osmo.git?rev=bccc1d16805ac446e2c32294d69a0090a2b85181#bccc1d16805ac446e2c32294d69a0090a2b85181" - -[[package]] -name = "pkg-config" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" - [[package]] name = "ppv-lite86" version = "0.2.21" @@ -964,7 +946,7 @@ checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] name = "xtask" -version = "0.1.1" +version = "0.1.2" dependencies = [ "serde", "serde_json", @@ -996,31 +978,3 @@ name = "zmij" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" - -[[package]] -name = "zstd" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "7.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" -dependencies = [ - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.16+zstd.1.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" -dependencies = [ - "cc", - "pkg-config", -] diff --git a/Cargo.toml b/Cargo.toml index f3af6902..7a57af18 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,13 +3,17 @@ members = ["crates/parser", "crates/cli", "crates/xtask"] resolver = "3" [workspace.package] -version = "0.1.1" +version = "0.1.2" license = "MIT" authors = [ "Javier Osorio M ", "Julien Wist ", "Jorge Osorio M " ] +edition = "2024" +rust-version = "1.85" +repository = "https://github.com/phenological/ionic" +homepage = "https://github.com/phenological/ionic" [workspace.dependencies] base64 = "0.22.1" @@ -19,8 +23,7 @@ quick-xml = "0.40.1" serde = { version = "1", features = ["derive"] } serde_json = "1.0" clap = "4.6.1" -zstd = "0.13.3" -osmo = { git = "https://github.com/josoriom/osmo.git", rev = "bccc1d16805ac446e2c32294d69a0090a2b85181", default-features = false } +cosmoz = { version = "0.1.0", features = ["std"] } regex = "1.12.4" rayon = "1.12.0" itoa = "1.0" diff --git a/README.md b/README.md index 504664c8..1357f93c 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ output.flush()?; ```rust pub struct WriteOptions { - pub compression_level: u8, // 0 = off, 1..=22 zstd; default 22 + pub compression_level: u8, // 0 = off, 1..=22 zstd; default 12 pub force_f32: bool, // narrow f64 arrays to f32 (lossy); default false pub block_size: usize, // target uncompressed block bytes; default 1 MiB pub parallel: bool, // default true @@ -138,9 +138,10 @@ A fixed 1024-byte header stores the byte offset of every section. A reader uses ## Portability The library depends only on standard byte operations: no HDF5, no storage engine, nothing -to install alongside a file. On `wasm32-unknown-unknown` the build swaps `zstd` for the -pure-Rust `ruzstd` and drops `rayon` and `memmap2`, so the same reader compiles into a -browser bundle. That is what [ion-beam](https://github.com/phenological/ion-beam) runs on. +to install alongside a file. Compression uses the pure-Rust [cosmoz](https://crates.io/crates/cosmoz) +on every target, so native and `wasm32-unknown-unknown` share the same zstd code. The wasm build +drops `rayon` and `memmap2`, and browser conversions use compression level 1. That is what +[ion-beam](https://github.com/phenological/ion-beam) runs on. ## Related projects diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 5c9019bd..92f88567 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -2,19 +2,20 @@ name = "cli" version.workspace = true license.workspace = true -edition = "2024" +edition.workspace = true +publish = false [dependencies] serde = { workspace = true } serde_json = { workspace = true } clap = { workspace = true, features = ["derive"] } -ionic = { path = "../parser" } +ionic = { package = "ionic-rs", path = "../parser", version = "0.1.2" } regex = { workspace = true } rayon = { workspace = true } mimalloc = { version = "0.1.48" } crc32fast = "1.5.0" memmap2 = "0.9" -zstd = { workspace = true } +cosmoz = { workspace = true } [[bin]] name = "ionic" diff --git a/crates/cli/src/README.MD b/crates/cli/src/README.MD index 2caf1878..c27080f9 100644 --- a/crates/cli/src/README.MD +++ b/crates/cli/src/README.MD @@ -67,7 +67,7 @@ the relative folder structure under `--output-path`. - `--level <0..22>` - zstd compression level for the data blocks. `0` disables compression. - Default: `22`. + Default: `12`. - `--mz-window ` - Width, in Da (m/z units), of the windowed layout. A spectrum's m/z and intensity @@ -153,7 +153,7 @@ You may pass **at most one** of the following: Defaults: -- `--level 22`, `--mz-window 250`, `--block-size 8`, `--storage disk` +- `--level 12`, `--mz-window 250`, `--block-size 8`, `--storage disk` Run (defaults): diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 2777e105..be354064 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -149,7 +149,7 @@ struct ConvertArgs { #[arg( long = "level", conflicts_with = "update", - default_value_t = 22, + default_value_t = 12, value_parser = clap::value_parser!(u8).range(0..=22), help = "Compression level 0–22 (0 = off)" )] @@ -228,7 +228,6 @@ struct ConvertArgs { which: ConvertWhich, } - #[derive(Args)] struct ConvertWhich { #[arg(long = "mzml-to-ion", help = "Convert mzML to ion (default)")] @@ -973,8 +972,7 @@ fn convert(cmd: ConvertArgs) -> Result<(), String> { let t_all = Instant::now(); - let default_mzml_to_ion = - !cmd.which.mzml_to_ion && !cmd.which.ion_to_mzml && !cmd.which.update; + let default_mzml_to_ion = !cmd.which.mzml_to_ion && !cmd.which.ion_to_mzml && !cmd.which.update; let mzml_to_ion = cmd.which.mzml_to_ion || default_mzml_to_ion; let ion_to_mzml = cmd.which.ion_to_mzml; diff --git a/crates/cli/src/utilities/header.rs b/crates/cli/src/utilities/header.rs index f2e9907d..c297b7f4 100644 --- a/crates/cli/src/utilities/header.rs +++ b/crates/cli/src/utilities/header.rs @@ -1,5 +1,7 @@ use std::{fs::File, io::IsTerminal, path::Path, sync::OnceLock}; +use cosmoz::{DecodeWorkspace, decompress}; + use ionic::ion::{ CODEC_NONE, CODEC_ZSTD, FILE_SIGNATURE, FILE_TRAILER, HEADER_SIZE, get_version_from_header, is_supported, @@ -88,7 +90,16 @@ fn read_fixed_section( if plain_len == 0 { return Ok(Vec::new()); } - zstd::bulk::decompress(stored, plain_len).map_err(|e| format!("decompress failed: {e}")) + decompress_section(stored, plain_len) +} + +fn decompress_section(stored: &[u8], plain_len: usize) -> Result, String> { + let mut plain = vec![0u8; plain_len]; + let mut workspace = DecodeWorkspace::new_boxed(); + let written = decompress(stored, &mut plain, &mut workspace) + .map_err(|e| format!("decompress failed: {e:?}"))?; + plain.truncate(written); + Ok(plain) } fn read_address_section( @@ -117,10 +128,24 @@ fn resolve_fixed_sections(bytes: &[u8], header: &[u8]) -> FixedSections { let spec_count = u64_at(header, 256); let chrom_count = u64_at(header, 264); - let spec_summary = - read_fixed_section(bytes, header, 48, 56, spec_count, SPEC_SUMMARY_ROW, compressed); - let spec_entries = - read_fixed_section(bytes, header, 64, 72, spec_count, INDEX_ENTRY_ROW, compressed); + let spec_summary = read_fixed_section( + bytes, + header, + 48, + 56, + spec_count, + SPEC_SUMMARY_ROW, + compressed, + ); + let spec_entries = read_fixed_section( + bytes, + header, + 64, + 72, + spec_count, + INDEX_ENTRY_ROW, + compressed, + ); let spec_addresses = read_address_section(bytes, header, 80, 88, &spec_entries, compressed); let chrom_summary = read_fixed_section( bytes, @@ -131,8 +156,15 @@ fn resolve_fixed_sections(bytes: &[u8], header: &[u8]) -> FixedSections { SPEC_SUMMARY_ROW, compressed, ); - let chrom_entries = - read_fixed_section(bytes, header, 128, 136, chrom_count, INDEX_ENTRY_ROW, compressed); + let chrom_entries = read_fixed_section( + bytes, + header, + 128, + 136, + chrom_count, + INDEX_ENTRY_ROW, + compressed, + ); let chrom_addresses = read_address_section(bytes, header, 144, 152, &chrom_entries, compressed); FixedSections { @@ -145,7 +177,11 @@ fn resolve_fixed_sections(bytes: &[u8], header: &[u8]) -> FixedSections { } } -fn check_fixed(section: &Result, String>, record: u64, count: Option) -> Result<(), String> { +fn check_fixed( + section: &Result, String>, + record: u64, + count: Option, +) -> Result<(), String> { let plain = section.as_ref().map_err(|why| why.clone())?; let len = plain.len() as u64; if len % record != 0 { @@ -154,7 +190,10 @@ fn check_fixed(section: &Result, String>, record: u64, count: Option raw.to_vec(), CODEC_ZSTD => { let plain_len = u64_at(header, plain_len_at) as usize; - zstd::bulk::decompress(raw, plain_len).map_err(|e| format!("decompress failed: {e}"))? + decompress_section(raw, plain_len)? } _ => return Err("unknown codec".into()), }; @@ -586,11 +625,19 @@ fn section_check_results(view: &HeaderView<'_>) -> [Result<(), String>; 8] { let chrom_count = view.read_header_u64(264); [ view.spec_window_dir.clone().map(|_| ()), - check_fixed(&view.fixed.spec_summary, SPEC_SUMMARY_ROW as u64, Some(spec_count)), + check_fixed( + &view.fixed.spec_summary, + SPEC_SUMMARY_ROW as u64, + Some(spec_count), + ), check_fixed(&view.fixed.spec_entries, 16, Some(spec_count)), check_fixed(&view.fixed.spec_addresses, 32, None), view.chrom_window_dir.clone().map(|_| ()), - check_fixed(&view.fixed.chrom_summary, SPEC_SUMMARY_ROW as u64, Some(chrom_count)), + check_fixed( + &view.fixed.chrom_summary, + SPEC_SUMMARY_ROW as u64, + Some(chrom_count), + ), check_fixed(&view.fixed.chrom_entries, 16, Some(chrom_count)), check_fixed(&view.fixed.chrom_addresses, 32, None), ] diff --git a/crates/parser/Cargo.toml b/crates/parser/Cargo.toml index 39c4136b..262ec698 100644 --- a/crates/parser/Cargo.toml +++ b/crates/parser/Cargo.toml @@ -1,8 +1,21 @@ [package] -name = "ionic" +name = "ionic-rs" +description = "Streamable binary format for mass-spectrometry data: lossless mzML conversion, random access, WebAssembly support" version.workspace = true +authors.workspace = true license.workspace = true -edition = "2024" +edition.workspace = true +rust-version.workspace = true +repository.workspace = true +homepage.workspace = true +documentation = "https://docs.rs/ionic-rs" +readme = "../../README.md" +keywords = ["mzml", "wasm"] +categories = ["parser-implementations", "encoding", "wasm"] +include = ["src/**/*", "Cargo.toml", "LICENSE"] + +[lib] +name = "ionic" [dependencies] serde = { workspace = true } @@ -13,18 +26,14 @@ quick-xml = { workspace = true } serde_json = { workspace = true } hashbrown = "0.17.1" itoa = { workspace = true } +cosmoz = { workspace = true } crc32fast = "1.5.0" sha1 = "0.11" bytemuck = "1" [target.'cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))'.dependencies] -zstd = { workspace = true } rayon = { workspace = true } memmap2 = "0.9" -[target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dependencies] -osmo = { workspace = true, features = ["std"] } - [dev-dependencies] proptest = "1" -sha1 = "0.11" diff --git a/crates/parser/LICENSE b/crates/parser/LICENSE new file mode 120000 index 00000000..30cff740 --- /dev/null +++ b/crates/parser/LICENSE @@ -0,0 +1 @@ +../../LICENSE \ No newline at end of file diff --git a/crates/parser/src/convert.rs b/crates/parser/src/convert.rs index fba8b52e..4da310d4 100644 --- a/crates/parser/src/convert.rs +++ b/crates/parser/src/convert.rs @@ -138,7 +138,7 @@ fn mzml_to_ion(input: &Input<'_>, options: ConvertOptions) -> IonResult, Arc<[u8]>, Arc<[u8]>, Arc<[u8]>, Arc<[u8]>, Arc<[u8]>) = - if header.compression_codec == CODEC_ZSTD { - let spec_summary_plain = decompress_fixed_section( - &spec_summary_buf, - header.spectrum_count as usize * SPEC_SUMMARY_SIZE, - config.decompression_limit, - "spec_summary", - )?; - let spec_entries_plain = decompress_fixed_section( - &spec_entries_buf, - header.spectrum_count as usize * INDEX_ENTRY_BYTES, - config.decompression_limit, - "spec_entries", - )?; - let spec_array_addresses_plain = decompress_fixed_section( - &spec_array_addresses, - max_address_index(&spec_entries_plain) as usize * ARRAY_ADDRESS_BYTES, - config.decompression_limit, - "spec_array_addresses", - )?; - let chrom_summary_plain = decompress_fixed_section( - &chrom_summary_buf, - header.chrom_count as usize * CHROM_SUMMARY_SIZE, - config.decompression_limit, - "chrom_summary", - )?; - let chrom_entries_plain = decompress_fixed_section( - &chrom_entries_buf, - header.chrom_count as usize * INDEX_ENTRY_BYTES, - config.decompression_limit, - "chrom_entries", - )?; - let chrom_array_addresses_plain = decompress_fixed_section( - &chrom_array_addresses, - max_address_index(&chrom_entries_plain) as usize * ARRAY_ADDRESS_BYTES, - config.decompression_limit, - "chrom_array_addresses", - )?; - ( - spec_summary_plain, - spec_entries_plain, - spec_array_addresses_plain, - chrom_summary_plain, - chrom_entries_plain, - chrom_array_addresses_plain, - ) - } else { - ( - spec_summary_buf.into_arc(), - spec_entries_buf.into_arc(), - spec_array_addresses.into_arc(), - chrom_summary_buf.into_arc(), - chrom_entries_buf.into_arc(), - chrom_array_addresses.into_arc(), - ) - }; + ): ( + Arc<[u8]>, + Arc<[u8]>, + Arc<[u8]>, + Arc<[u8]>, + Arc<[u8]>, + Arc<[u8]>, + ) = if header.compression_codec == CODEC_ZSTD { + let spec_summary_plain = decompress_fixed_section( + &spec_summary_buf, + header.spectrum_count as usize * SPEC_SUMMARY_SIZE, + config.decompression_limit, + "spec_summary", + )?; + let spec_entries_plain = decompress_fixed_section( + &spec_entries_buf, + header.spectrum_count as usize * INDEX_ENTRY_BYTES, + config.decompression_limit, + "spec_entries", + )?; + let spec_array_addresses_plain = decompress_fixed_section( + &spec_array_addresses, + max_address_index(&spec_entries_plain) as usize * ARRAY_ADDRESS_BYTES, + config.decompression_limit, + "spec_array_addresses", + )?; + let chrom_summary_plain = decompress_fixed_section( + &chrom_summary_buf, + header.chrom_count as usize * CHROM_SUMMARY_SIZE, + config.decompression_limit, + "chrom_summary", + )?; + let chrom_entries_plain = decompress_fixed_section( + &chrom_entries_buf, + header.chrom_count as usize * INDEX_ENTRY_BYTES, + config.decompression_limit, + "chrom_entries", + )?; + let chrom_array_addresses_plain = decompress_fixed_section( + &chrom_array_addresses, + max_address_index(&chrom_entries_plain) as usize * ARRAY_ADDRESS_BYTES, + config.decompression_limit, + "chrom_array_addresses", + )?; + ( + spec_summary_plain, + spec_entries_plain, + spec_array_addresses_plain, + chrom_summary_plain, + chrom_entries_plain, + chrom_array_addresses_plain, + ) + } else { + ( + spec_summary_buf.into_arc(), + spec_entries_buf.into_arc(), + spec_array_addresses.into_arc(), + chrom_summary_buf.into_arc(), + chrom_entries_buf.into_arc(), + chrom_array_addresses.into_arc(), + ) + }; check_address_table( &spec_entries_plain, diff --git a/crates/parser/src/ion/decoder/decode/tests.rs b/crates/parser/src/ion/decoder/decode/tests.rs index fe8cf149..3ddc0eed 100644 --- a/crates/parser/src/ion/decoder/decode/tests.rs +++ b/crates/parser/src/ion/decoder/decode/tests.rs @@ -1125,8 +1125,7 @@ fn split_mz_array_roundtrips_with_disk_staged_bounds() { let mz: Vec = (0..n).map(|i| 100.0 + i as f64 * 0.001).collect(); let int: Vec = (0..n).map(|i| (i % 1000) as f64).collect(); - let encoded = - encode_one_spectrum_windowed(mz.clone(), int.clone(), 10.0); + let encoded = encode_one_spectrum_windowed(mz.clone(), int.clone(), 10.0); let mut decoder = IonReader::open(&encoded, ReadOptions::default()).unwrap(); diff --git a/crates/parser/src/ion/decoder/utilities/common.rs b/crates/parser/src/ion/decoder/utilities/common.rs index f9756919..5edcfd33 100644 --- a/crates/parser/src/ion/decoder/utilities/common.rs +++ b/crates/parser/src/ion/decoder/utilities/common.rs @@ -1,5 +1,4 @@ -#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))] -use zstd::zstd_safe; +use cosmoz::{DecodeWorkspace, decompress, get_frame_compressed_size}; use crate::ion::{ IonError, IonResult, @@ -81,32 +80,6 @@ pub(crate) fn read_f64_vec(bytes: &[u8], pos: &mut usize, n: usize) -> IonResult } #[inline] -#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))] -pub(crate) fn decompress_zstd( - comp: &[u8], - expected: usize, - budget: DecompressionLimit, -) -> IonResult> { - if expected == 0 { - return Ok(Vec::new()); - } - - budget.validate(comp.len(), expected)?; - - let mut out: Vec = Vec::with_capacity(expected); - - let actual = zstd_safe::decompress(&mut out, comp) - .map_err(|e| IonError::from(format!("zstd decode failed: {e:?}")))?; - - if actual != expected { - return Err(format!("zstd: bad decoded size (got={actual}, expected={expected})").into()); - } - - Ok(out) -} - -#[inline] -#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] pub(crate) fn decompress_zstd( comp: &[u8], expected: usize, @@ -119,8 +92,8 @@ pub(crate) fn decompress_zstd( budget.validate(comp.len(), expected)?; let mut out = vec![0u8; expected]; - let mut workspace = osmo::DecodeWorkspace::new_boxed(); - let actual = osmo::decompress(comp, &mut out, &mut workspace) + let mut workspace = DecodeWorkspace::new_boxed(); + let actual = decompress(comp, &mut out, &mut workspace) .map_err(|err| IonError::from(format!("zstd decode failed: {err:?}")))?; if actual != expected { @@ -140,8 +113,7 @@ pub(crate) fn decompress_zstd_allow_aligned_padding( return Ok(Vec::new()); } - #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))] - if let Ok(n) = zstd_safe::find_frame_compressed_size(input) + if let Ok(n) = get_frame_compressed_size(input) && n > 0 && n <= input.len() && let Ok(v) = decompress_zstd(&input[..n], expected, budget) @@ -258,23 +230,25 @@ pub(crate) fn parse_accession_tail(accession: Option<&str>) -> AccessionTail { } #[cfg(test)] -#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))] mod tests { - use zstd::zstd_safe; + use cosmoz::{CompressOptions, EncodeWorkspace, compress, get_max_compressed_size}; use super::*; use crate::ion::decoder::utilities::decompression_limit::DecompressionLimit; - fn compress(data: &[u8]) -> Vec { - let mut out = Vec::with_capacity(zstd_safe::compress_bound(data.len())); - zstd_safe::compress(&mut out, data, 3).unwrap(); + fn compress_to_frame(data: &[u8]) -> Vec { + let options = CompressOptions::zstd(); + let mut workspace = EncodeWorkspace::new_boxed(); + let mut out = vec![0u8; get_max_compressed_size(data.len(), &options)]; + let written = compress(data, &mut out, &options, &mut workspace).unwrap(); + out.truncate(written); out } #[test] fn decompress_zstd_round_trips_bytes() { let original: Vec = (0..1000u32).map(|i| (i % 251) as u8).collect(); - let compressed = compress(&original); + let compressed = compress_to_frame(&original); let restored = decompress_zstd(&compressed, original.len(), DecompressionLimit::default()).unwrap(); assert_eq!(restored, original); diff --git a/crates/parser/src/ion/decoder/utilities/parse_metadata.rs b/crates/parser/src/ion/decoder/utilities/parse_metadata.rs index 1e1cce62..a801f2e5 100644 --- a/crates/parser/src/ion/decoder/utilities/parse_metadata.rs +++ b/crates/parser/src/ion/decoder/utilities/parse_metadata.rs @@ -266,15 +266,15 @@ fn rebuild_value_indices(value_kinds: &[u8]) -> IonResult> { match kind { 0 => { value_indices.push(next_numeric_ordinal); - next_numeric_ordinal = next_numeric_ordinal - .checked_add(1) - .ok_or_else(|| crate::ion::IonError::from("numeric value ordinal overflows u32"))?; + next_numeric_ordinal = next_numeric_ordinal.checked_add(1).ok_or_else(|| { + crate::ion::IonError::from("numeric value ordinal overflows u32") + })?; } 1 => { value_indices.push(next_string_ordinal); - next_string_ordinal = next_string_ordinal - .checked_add(1) - .ok_or_else(|| crate::ion::IonError::from("string value ordinal overflows u32"))?; + next_string_ordinal = next_string_ordinal.checked_add(1).ok_or_else(|| { + crate::ion::IonError::from("string value ordinal overflows u32") + })?; } _ => value_indices.push(0), } diff --git a/crates/parser/src/ion/encoder/encode.rs b/crates/parser/src/ion/encoder/encode.rs index d17a95b6..23f9e8d2 100644 --- a/crates/parser/src/ion/encoder/encode.rs +++ b/crates/parser/src/ion/encoder/encode.rs @@ -158,6 +158,15 @@ pub(crate) fn extract_chrom_summary(chrom: &Chromatogram) -> ChromatogramSummary summary } +pub(crate) fn get_supported_compression_level(compression_level: u8) -> u8 { + match compression_level { + 0..=5 => 1, + 6..=10 => 9, + 11..=16 => 12, + _ => 22, + } +} + pub(crate) fn allow_compression_level(compression_level: u8) -> IonResult<()> { if compression_level > 22 { return Err(format!("compression_level must be 0-22, got {compression_level}").into()); @@ -178,7 +187,7 @@ pub struct WriteOptions { impl Default for WriteOptions { fn default() -> Self { Self { - compression_level: 22, + compression_level: 12, force_f32: false, block_size: TARGET_BLOCK_UNCOMPRESSED_BYTES, parallel: true, diff --git a/crates/parser/src/ion/encoder/utilities/block_writer.rs b/crates/parser/src/ion/encoder/utilities/block_writer.rs index f2455947..1d6d1125 100644 --- a/crates/parser/src/ion/encoder/utilities/block_writer.rs +++ b/crates/parser/src/ion/encoder/utilities/block_writer.rs @@ -1,14 +1,15 @@ use std::collections::HashMap; +use cosmoz::{CompressOptions, EncodeWorkspace, compress, get_max_compressed_size}; #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))] use rayon::prelude::*; -#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))] -use zstd::{bulk::Compressor as ZstdCompressor, zstd_safe::compress_bound}; #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))] use crate::ion::encoder::utilities::output::RawSpill; use crate::ion::{ - IonError, IonResult, byte_transpose::shuffle_with_tail, encoder::utilities::output::WriteBytes, + IonError, IonResult, + byte_transpose::shuffle_with_tail, + encoder::{encode::get_supported_compression_level, utilities::output::WriteBytes}, packing::PackingId, }; @@ -50,65 +51,40 @@ pub(crate) trait BlockCompressor { fn shuffle_bytes_into(&self, input: &[u8], output: &mut [u8], element_stride: usize); } -#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))] pub(crate) struct DefaultCompressor { - level: i32, - inner: ZstdCompressor<'static>, + level: u8, + workspace: Box, } -#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] -pub(crate) struct DefaultCompressor; - -#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))] impl DefaultCompressor { pub(crate) fn new(compression_level: i32) -> IonResult { - Ok(Self { - level: compression_level, - inner: ZstdCompressor::new(compression_level) - .map_err(|err| IonError::from(err.to_string()))?, - }) - } -} - -#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] -impl DefaultCompressor { - pub(crate) fn new(_compression_level: i32) -> IonResult { - Ok(Self) - } -} - -#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))] -impl BlockCompressor for DefaultCompressor { - fn compress(&mut self, input: &[u8], output: &mut Vec) -> IonResult { - output.clear(); - output.reserve(compress_bound(input.len())); - self.inner - .compress_to_buffer(input, output) - .map_err(|err| IonError::from(err.to_string())) - } - - fn fork(&self) -> IonResult { - Self::new(self.level) - } - - fn shuffle_bytes_into(&self, input: &[u8], output: &mut [u8], element_stride: usize) { - shuffle_with_tail(input, output, element_stride); + let level = get_supported_compression_level(compression_level.clamp(0, 22) as u8); + let workspace = EncodeWorkspace::new_boxed_for_level(level) + .map_err(|err| IonError::from(format!("zstd start error: {err:?}")))?; + Ok(Self { level, workspace }) } } -#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] impl BlockCompressor for DefaultCompressor { fn compress(&mut self, input: &[u8], output: &mut Vec) -> IonResult { - let options = osmo::CompressOptions::zstd(); + let options = CompressOptions { + level: self.level, + with_checksum: false, + ..Default::default() + }; output.clear(); - output.resize(osmo::get_max_compressed_size(input.len(), &options), 0); - let mut workspace = osmo::EncodeWorkspace::new_boxed(); - let written = osmo::compress(input, output, &options, &mut workspace) + output.resize(get_max_compressed_size(input.len(), &options), 0); + let written = compress(input, output, &options, &mut self.workspace) .map_err(|err| IonError::from(format!("zstd encode failed: {err:?}")))?; output.truncate(written); Ok(written) } + #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))] + fn fork(&self) -> IonResult { + Self::new(self.level as i32) + } + fn shuffle_bytes_into(&self, input: &[u8], output: &mut [u8], element_stride: usize) { shuffle_with_tail(input, output, element_stride); } diff --git a/crates/parser/src/ion/encoder/utilities/meta_collector/mod.rs b/crates/parser/src/ion/encoder/utilities/meta_collector/mod.rs index 116a3a26..c6a677f6 100644 --- a/crates/parser/src/ion/encoder/utilities/meta_collector/mod.rs +++ b/crates/parser/src/ion/encoder/utilities/meta_collector/mod.rs @@ -6,8 +6,8 @@ mod tests; pub(crate) use grouper::{GroupedSection, MetaGrouper, serialize_global_meta_with_counts}; pub(crate) use schema::MzmlListItem; -#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))] -use zstd::bulk::compress as zstd_compress; + +use cosmoz::{CompressOptions, EncodeWorkspace, compress, get_max_compressed_size}; use crate::{ accessions::{INTENSITY_ARRAY, MZ_ARRAY, TIME_ARRAY}, @@ -18,6 +18,7 @@ use crate::{ parse_accession_tail, }, decoder::decode::MetadatumValue, + encoder::encode::get_supported_compression_level, utilities::assign_attributes_into, }, mzml::{ @@ -614,24 +615,19 @@ impl MetadataWriter for PackedMetaBuilder { } } -#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))] -pub(crate) fn compress_bytes_if_enabled(bytes: Vec, level: u8) -> Vec { - if level == 0 { - bytes - } else { - zstd_compress(&bytes, level as i32).expect("zstd compression failed") - } -} - -#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] pub(crate) fn compress_bytes_if_enabled(bytes: Vec, level: u8) -> Vec { if level == 0 { return bytes; } - let options = osmo::CompressOptions::zstd(); - let mut out = vec![0u8; osmo::get_max_compressed_size(bytes.len(), &options)]; - let mut workspace = osmo::EncodeWorkspace::new_boxed(); - let written = osmo::compress(&bytes, &mut out, &options, &mut workspace) + let options = CompressOptions { + level: get_supported_compression_level(level), + with_checksum: false, + ..Default::default() + }; + let mut workspace = + EncodeWorkspace::new_boxed_for_level(options.level).expect("zstd compression failed"); + let mut out = vec![0u8; get_max_compressed_size(bytes.len(), &options)]; + let written = compress(&bytes, &mut out, &options, &mut workspace) .expect("zstd compression failed"); out.truncate(written); out diff --git a/crates/parser/src/ion/encoder/utilities/meta_collector/tests.rs b/crates/parser/src/ion/encoder/utilities/meta_collector/tests.rs index 2266195f..1102ea5c 100644 --- a/crates/parser/src/ion/encoder/utilities/meta_collector/tests.rs +++ b/crates/parser/src/ion/encoder/utilities/meta_collector/tests.rs @@ -185,7 +185,9 @@ fn collector_global_meta_on_empty_mzml_produces_run_buffer() { #[test] fn grouped_metadata_keeps_values_local_to_each_group() { use crate::ion::{ - DecompressionLimit, decoder::decode::MetadatumValue, format::CODEC_NONE, + DecompressionLimit, + decoder::decode::MetadatumValue, + format::CODEC_NONE, meta_groups::MetaTotals, utilities::{MetaGroupReader, meta_column_layout::MetaColumnLayout}, }; @@ -258,7 +260,9 @@ fn grouped_bytes(grouped: &grouper::GroupedSection) -> &[u8] { #[test] fn metadata_reader_rejects_wrong_uncompressed_total() { use crate::ion::{ - DecompressionLimit, format::CODEC_NONE, meta_groups::MetaTotals, + DecompressionLimit, + format::CODEC_NONE, + meta_groups::MetaTotals, utilities::{MetaGroupReader, meta_column_layout::MetaColumnLayout}, }; @@ -286,7 +290,9 @@ fn metadata_reader_rejects_wrong_uncompressed_total() { #[test] fn metadata_reader_rejects_wrong_row_total() { use crate::ion::{ - DecompressionLimit, format::CODEC_NONE, meta_groups::MetaTotals, + DecompressionLimit, + format::CODEC_NONE, + meta_groups::MetaTotals, utilities::{MetaGroupReader, meta_column_layout::MetaColumnLayout}, }; diff --git a/crates/parser/src/ion/encoder/utilities/output.rs b/crates/parser/src/ion/encoder/utilities/output.rs index 8f2aa78f..a07bd65b 100644 --- a/crates/parser/src/ion/encoder/utilities/output.rs +++ b/crates/parser/src/ion/encoder/utilities/output.rs @@ -7,8 +7,10 @@ use std::{ }; #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))] -use zstd::stream::write::Encoder; +use cosmoz::StreamWriter; +#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))] +use crate::ion::encoder::encode::get_supported_compression_level; use crate::ion::{IonError, IonResult}; pub trait WriteBytes { @@ -91,7 +93,7 @@ static NEXT_SECTION_ID: AtomicU64 = AtomicU64::new(0); #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))] pub(crate) struct SpilledSection { - packer: Option>>, + packer: Option>>, path: PathBuf, len: u64, } @@ -196,11 +198,12 @@ impl SectionChunk { .write(true) .create_new(true) .open(&path) - .map_err(|err| { - IonError::from(format!("cannot create '{}': {err}", path.display())) - })?; - let packer = Encoder::new(BufWriter::with_capacity(1 << 20, file), level as i32) - .map_err(|err| IonError::from(format!("zstd start error: {err}")))?; + .map_err(|err| IonError::from(format!("cannot create '{}': {err}", path.display())))?; + let packer = StreamWriter::new( + BufWriter::with_capacity(1 << 20, file), + get_supported_compression_level(level), + ) + .map_err(|err| IonError::from(format!("zstd start error: {err}")))?; Ok(SectionChunk::Spilled(SpilledSection { packer: Some(packer), path, @@ -253,9 +256,9 @@ impl SectionChunk { match self { SectionChunk::Memory(buffer) => Ok(buffer), #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))] - SectionChunk::Spilled(_) => { - Err(IonError::from("a spilled section cannot be read into memory")) - } + SectionChunk::Spilled(_) => Err(IonError::from( + "a spilled section cannot be read into memory", + )), } } @@ -311,6 +314,8 @@ fn pad_to_alignment(output: &mut dyn WriteBytes) -> IonResult { #[cfg(all(test, not(all(target_arch = "wasm32", not(target_os = "wasi")))))] mod tests { + use cosmoz::{DecodeWorkspace, decompress}; + use super::*; fn row(seed: u64) -> [u8; 64] { @@ -383,7 +388,10 @@ mod tests { assert!(stored < written.len() as u64); assert_eq!(crc32, crc32fast::hash(&output)); - let restored = zstd::decode_all(&output[..]).unwrap(); + let mut restored = vec![0u8; written.len()]; + let mut workspace = DecodeWorkspace::new_boxed(); + let restored_length = decompress(&output, &mut restored, &mut workspace).unwrap(); + assert_eq!(restored_length, written.len()); assert_eq!(restored, written); } diff --git a/crates/parser/tests/api_contract.rs b/crates/parser/tests/api_contract.rs index 0c721372..710c4400 100644 --- a/crates/parser/tests/api_contract.rs +++ b/crates/parser/tests/api_contract.rs @@ -9,8 +9,8 @@ use common::{ use ionic::{ ByteRange, BytesSource, CallbackSource, Range, ScanSource, ion::{ - IonError, IonReader, IonWriter, MemoryReader, ReadOptions, SectionStorage, TARGET_BLOCK_UNCOMPRESSED_BYTES, - WriteOptions, open_ranges, + IonError, IonReader, IonWriter, MemoryReader, ReadOptions, SectionStorage, + TARGET_BLOCK_UNCOMPRESSED_BYTES, WriteOptions, open_ranges, }, }; diff --git a/crates/parser/tests/convert.rs b/crates/parser/tests/convert.rs index d6a1b931..47d212f5 100644 --- a/crates/parser/tests/convert.rs +++ b/crates/parser/tests/convert.rs @@ -38,7 +38,7 @@ fn options_with_kind(kind: ConvertKind) -> ConvertOptions { } fn expected_ion_bytes() -> Vec { - encode_to_ion(&parse_test_file(MZML), 22, false) + encode_to_ion(&parse_test_file(MZML), 12, false) } fn expected_mzml_xml() -> Vec { diff --git a/crates/parser/tests/ion_writer_differ.rs b/crates/parser/tests/ion_writer_differ.rs index 0037a3ca..ea61f424 100644 --- a/crates/parser/tests/ion_writer_differ.rs +++ b/crates/parser/tests/ion_writer_differ.rs @@ -5,7 +5,8 @@ use std::path::PathBuf; use common::canonical_diff_paths; use ionic::{ ion::{ - IonReader, ReadOptions, SectionStorage, TARGET_BLOCK_UNCOMPRESSED_BYTES, WriteOptions, write_mzml_to_ion, + IonReader, ReadOptions, SectionStorage, TARGET_BLOCK_UNCOMPRESSED_BYTES, WriteOptions, + write_mzml_to_ion, }, mzml::parse_mzml::parse_mzml, }; diff --git a/crates/parser/tests/mz_order_rejection.rs b/crates/parser/tests/mz_order_rejection.rs index 5c85ff58..d3fb0a03 100644 --- a/crates/parser/tests/mz_order_rejection.rs +++ b/crates/parser/tests/mz_order_rejection.rs @@ -5,8 +5,8 @@ use std::sync::Arc; use ionic::{ BytesSource, ion::{ - IonReader, IonWriter, MemoryReader, Range, ReadOptions, SectionStorage, TARGET_BLOCK_UNCOMPRESSED_BYTES, - WriteOptions, + IonReader, IonWriter, MemoryReader, Range, ReadOptions, SectionStorage, + TARGET_BLOCK_UNCOMPRESSED_BYTES, WriteOptions, }, mzml::structs::{ BinaryDataArray, BinaryDataArrayList, CvParam, MzML, NumericArray, Run, Spectrum, diff --git a/crates/xtask/Cargo.toml b/crates/xtask/Cargo.toml index 94da4929..a7693619 100644 --- a/crates/xtask/Cargo.toml +++ b/crates/xtask/Cargo.toml @@ -2,7 +2,7 @@ name = "xtask" version.workspace = true license.workspace = true -edition = "2024" +edition.workspace = true publish = false [dependencies] From f10779e3c6005e89c6241ea4ff1847eb15504099 Mon Sep 17 00:00:00 2001 From: josoriom Date: Thu, 17 Sep 2026 16:16:32 +0800 Subject: [PATCH 3/3] chore: update config.json --- config.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/config.json b/config.json index 11aca9bc..2e6cee8b 100644 --- a/config.json +++ b/config.json @@ -1,4 +1,12 @@ [ + { + "package": "0.1.2", + "format": { + "current": 1, + "min_supported": 0, + "max_supported": 1 + } + }, { "package": "0.1.1", "format": {