Dependency discussion per CONTRIBUTING.md, sub-issue of #201.
Goal
Accept compressed FASTQ input beyond gzip: bzip2, xz, zstd, without the user having to route it
through --readFilesCommand.
Where we are today
src/io/fastq.rs:88-110:
- compression is detected by file extension (
.gz / .gzip), not by content
- the only decoder is gzip, via
flate2::read::GzDecoder
- anything else has to go through
--readFilesCommand (external process, as STAR does)
Two consequences: a .fq.zst is read as plain text and fails on garbage, and a gzip file with an
unusual name silently gets the same treatment. Related: the single-member gzip bug filed alongside
this issue.
Candidate
niffler 3.0.1, MIT/Apache-2.0, 532k downloads, released
2026-04-29.
- Detection by magic bytes, not extension:
bytes2type peeks 5 bytes and matches 1f 8b
(gzip), 42 5a (bzip2), 28 b5 2f fd (zstd), fd 37 7a 58 5a (xz), falling back to
Format::No = pass through as plain.
- Gzip goes through
flate2::read::MultiGzDecoder (src/basic/compression.rs:54), so adopting it
fixes the multi-member truncation bug by construction.
niffler::send::get_reader / from_path return Box<dyn Read + Send>, which drops straight into
the existing Box<dyn BufRead + Send> in FastqReader::open via a BufReader::with_capacity.
The part that needs a decision: backends
niffler's default feature set is bgz + bz2 + gz + lzma + zstd, each with the upstream
crate's default features. That pulls in four more codecs, and for a crate published to crates.io
and built on five platforms including Windows, the backend of each is the whole question:
| Format |
niffler dep |
Backend reality |
| gz |
flate2 |
Already in the tree. niffler declares it default-features = false, so our zlib-rs choice survives feature unification |
| bz2 |
bzip2 0.6 |
Pure Rust by default: bzip2's own default = ["dep:libbz2-rs-sys"], with the C bzip2-sys as an opt-in. Good news |
| zstd |
zstd 0.13 |
C bindings (zstd-sys). Pure-Rust decode-only alternative exists: ruzstd 0.9.0, active, 57M downloads. niffler does not offer it as a backend |
| xz |
liblzma 0.4 |
C, non-optional liblzma-sys. Pure-Rust alternative lzma-rs is at 0.3.0 from 2023 |
| bgz |
bgzip 0.3.1 |
Stale (2023) and redundant: we already depend on noodles-bgzf 0.51 |
So the recommended shape if we adopt it is explicit, not default:
niffler = { version = "3", default-features = false, features = ["gz", "bz2"] }
and then a deliberate decision on zstd (C zstd via niffler, or ruzstd wired in ourselves) and on
xz (C only, in practice).
Alternative: do the sniff in-tree
The sniff is a 5-byte peek and a match; the decoders are one crate each. In-tree we could pick
ruzstd for pure-Rust zstd decode, which niffler cannot give us. Against that: niffler is 532k
downloads of battle-testing on exactly this problem, including the Send plumbing and the
pass-through case, and it is one dependency instead of three.
Reasonable split: take niffler for detection + gz + bz2, and treat zstd as its own decision.
STAR-compatibility note
STAR itself does not sniff; it expects --readFilesCommand (zcat, bunzip2, …). Auto-detection
is already a local convenience for gzip, so extending it is an extension of an existing divergence,
not a new one. Worth a line in DIVERGENCE.md and maintainer sign-off, since it changes what input
the tool silently accepts.
Checklist
Dependency discussion per
CONTRIBUTING.md, sub-issue of #201.Goal
Accept compressed FASTQ input beyond gzip: bzip2, xz, zstd, without the user having to route it
through
--readFilesCommand.Where we are today
src/io/fastq.rs:88-110:.gz/.gzip), not by contentflate2::read::GzDecoder--readFilesCommand(external process, as STAR does)Two consequences: a
.fq.zstis read as plain text and fails on garbage, and a gzip file with anunusual name silently gets the same treatment. Related: the single-member gzip bug filed alongside
this issue.
Candidate
niffler3.0.1, MIT/Apache-2.0, 532k downloads, released2026-04-29.
bytes2typepeeks 5 bytes and matches1f 8b(gzip),
42 5a(bzip2),28 b5 2f fd(zstd),fd 37 7a 58 5a(xz), falling back toFormat::No= pass through as plain.flate2::read::MultiGzDecoder(src/basic/compression.rs:54), so adopting itfixes the multi-member truncation bug by construction.
niffler::send::get_reader/from_pathreturnBox<dyn Read + Send>, which drops straight intothe existing
Box<dyn BufRead + Send>inFastqReader::openvia aBufReader::with_capacity.The part that needs a decision: backends
niffler'sdefaultfeature set isbgz + bz2 + gz + lzma + zstd, each with the upstreamcrate's default features. That pulls in four more codecs, and for a crate published to crates.io
and built on five platforms including Windows, the backend of each is the whole question:
flate2default-features = false, so ourzlib-rschoice survives feature unificationbzip2 0.6bzip2's owndefault = ["dep:libbz2-rs-sys"], with the Cbzip2-sysas an opt-in. Good newszstd 0.13zstd-sys). Pure-Rust decode-only alternative exists:ruzstd0.9.0, active, 57M downloads. niffler does not offer it as a backendliblzma 0.4liblzma-sys. Pure-Rust alternativelzma-rsis at0.3.0from 2023bgzip 0.3.1noodles-bgzf 0.51So the recommended shape if we adopt it is explicit, not default:
and then a deliberate decision on zstd (C
zstdvia niffler, orruzstdwired in ourselves) and onxz (C only, in practice).
Alternative: do the sniff in-tree
The sniff is a 5-byte peek and a match; the decoders are one crate each. In-tree we could pick
ruzstdfor pure-Rust zstd decode, which niffler cannot give us. Against that: niffler is 532kdownloads of battle-testing on exactly this problem, including the
Sendplumbing and thepass-through case, and it is one dependency instead of three.
Reasonable split: take
nifflerfor detection + gz + bz2, and treat zstd as its own decision.STAR-compatibility note
STAR itself does not sniff; it expects
--readFilesCommand(zcat,bunzip2, …). Auto-detectionis already a local convenience for gzip, so extending it is an extension of an existing divergence,
not a new one. Worth a line in
DIVERGENCE.mdand maintainer sign-off, since it changes what inputthe tool silently accepts.
Checklist
zstd(via niffler) vs pure-Rustruzstd(decode-only, in-tree)bgzfeature; BGZF stays onnoodles-bgzf--readFilesCommandworking and taking precedence over sniffingDIVERGENCE.mdentry + sign-off for the auto-detection behaviour