Skip to content

feat(raw-bam): unified view/editor API + close #272 gaps - #278

Merged
nh13 merged 20 commits into
mainfrom
nh/raw-bam-api-1-core
Apr 16, 2026
Merged

nh13 merged 20 commits into
mainfrom
nh/raw-bam-api-1-core

Conversation

@nh13

@nh13 nh13 commented Apr 15, 2026

Copy link
Copy Markdown
Member

Summary

Part 1 of 5 in the stacked series closing #272.

Introduces the six-type fgumi-raw-bam wrapper hierarchy and closes every gap catalogued in the issue. This PR is purely additive — the old free-function API remains pub so no downstream caller breaks. Migration to the new types, demotion of the old API, tests/benches, and perf optimizations land in follow-up PRs.

New types

Type Backing Capability
RawRecordView<'a> &'a [u8] All read accessors
RawRecordMut<'a> &'a mut [u8] Fixed-length writes
RawRecord Vec<u8> Length-changing edits
RawTagsView<'a> &'a [u8] Tag finders + zero-alloc iteration
RawTagsMut<'a> &'a mut [u8] Array element writes, in-place flips
RawTagsEditor<'a> &'a mut Vec<u8> + cached aux_offset Append/remove/update/resize

Issue 272 gaps closed (13 new methods)

Easy: bin(), set_bin(), update_float(), set_array_element_{u8,u16,i16,i32,f32}(), update_array_{u8,u16,i16,i32,f32}()
Medium (length-changing): set_read_name(), set_cigar_ops(), set_sequence_and_qualities()
Nice: RawTagsView::iter() (zero-allocation)

Test coverage in this PR

  • Port of all existing unit tests to the new API (in the same commit that introduces each type)
  • tests/noodles_roundtrip.rs — proptests cross-checking all three length-changing edits against noodles decode

Suggested reading order

  1. crates/fgumi-raw-bam/src/fields.rs — RawRecordView + RawRecordMut
  2. crates/fgumi-raw-bam/src/tags.rs — tag sub-types
  3. crates/fgumi-raw-bam/src/raw_bam_record.rs — RawRecord delegators + length-changing edits
  4. crates/fgumi-raw-bam/src/{cigar,sequence}.rs — view method impls

Test plan

  • cargo ci-test — workspace tests pass
  • cargo ci-fmt — clean
  • cargo ci-lint — clean
  • Noodles round-trip proptests validate length-changing edits

Stack

  • PR 1 (this): Core API + gap closures
  • PR 2: Migrate internal + downstream crates to the new API
  • PR 3: Demote old free-function API to pub(crate) (BREAKING)
  • PR 4: Tests + benchmarks
  • PR 5: Post-refactor perf optimizations

Closes #272 when the full stack lands.

Supersedes #277.

nh13 added 10 commits April 15, 2026 01:32
Adds the borrowed read-only view type that hosts all BAM read accessors.
Constructors: new (trust-caller, debug_assert) + try_new (validates min length).
Coexists with existing free functions; subsequent commits port methods over.

Refs #272
Adds method-form accessors for every fixed-offset BAM header field, plus
flag-bit convenience predicates (is_reverse, is_unmapped, ...). bin/set_bin
were absent from the free-function API and are added here for completeness.

Refs #272
Method-form CIGAR accessors with a zero-allocation cigar_ops_iter alongside
the existing cigar_ops_vec convenience. Length, end-position, and unclipped-5'
helpers exposed as inherent methods.

Refs #272
Method-form accessors for packed/iterated/decoded sequence and quality scores.
Zero-allocation sequence_packed, sequence_iter, quality_scores; sequence_vec
remains for convenience.

Refs #272
Borrowed fixed-length mutable view; pairs with RawRecordView for reads
via .view(). All header setters ported, plus the new set_bin from #272.

Refs #272
Borrowed read-only view over BAM aux data. Hosts find_string/int/uint8/float/
array, find_mi, find_mc plus contains/len/is_empty. RawRecordView::tags()
returns one. TagEntry struct defined for upcoming iter().

Refs #272
Closes the "Nice gap" from #272. AuxTagsIter walks the aux section yielding
TagEntry { tag, type_byte, value_bytes } without allocation. Uses the
existing tag_value_size for variable-length (Z/H/B) entries. Implements
IntoIterator for &RawTagsView to satisfy clippy::iter_without_into_iter.

Refs #272
@codecov

codecov Bot commented Apr 15, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.03%. Comparing base (3854d5c) to head (e416550).
⚠️ Report is 8 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #278      +/-   ##
==========================================
+ Coverage   89.88%   90.03%   +0.15%     
==========================================
  Files         120      122       +2     
  Lines       61585    61771     +186     
==========================================
+ Hits        55354    55615     +261     
+ Misses       6231     6156      -75     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@nh13
nh13 marked this pull request as ready for review April 15, 2026 08:38
@coderabbitai

coderabbitai Bot commented Apr 15, 2026 •

Copy link
Copy Markdown

Warning

Rate limit exceeded

@nh13 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 35 minutes and 0 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 35 minutes and 0 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5e3814db-0c92-46d6-80d6-e374194d016a

📥 Commits

Reviewing files that changed from the base of the PR and between 4eeb0d4 and e416550.

📒 Files selected for processing (3)
  • crates/fgumi-raw-bam/src/raw_bam_record.rs
  • crates/fgumi-raw-bam/src/tags.rs
  • crates/fgumi-raw-bam/tests/noodles_roundtrip.rs
📝 Walkthrough

Walkthrough

This change implements comprehensive raw-byte BAM record access and mutation APIs. It introduces RawRecordView and RawRecordMut as borrowed accessors over BAM record bytes, exposing fixed-length header fields, flag predicates, and coordinate helpers. RawRecord gains a large delegating API including CIGAR decoding, sequence/quality per-position access, and tag operations. New tag APIs (RawTagsView, RawTagsMut, RawTagsEditor) provide read-only queries, in-place mutations, and length-changing edits. Length-changing record editors (set_read_name, set_cigar_ops, set_sequence_and_qualities) splice and rebuild internal offsets. Tests cover delegation behavior, roundtrip encoding/decoding with noodles, and edge cases for mutations.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed Title directly describes the main change: introducing a unified view/editor API and closing issue #272 gaps in the raw-bam wrapper hierarchy.
Description check ✅ Passed Description clearly documents the PR as part 1 of a 5-part series, explains the new six-type wrapper hierarchy, lists gap closures with 13 new methods, and outlines test coverage and reading order.
Linked Issues check ✅ Passed All requirements from #272 are met: easy gaps (bin/set_bin, update_float, per-element array writes, same-length array updates), medium gaps (set_read_name, set_cigar_ops, set_sequence_and_qualities), and nice-to-have (RawTagsView::iter) are implemented with unit + proptest coverage.
Out of Scope Changes check ✅ Passed All changes align with issue #272 scope: six new wrapper types, 13 gap-closing methods, and test coverage for roundtrip validation. No unrelated modifications present.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch nh/raw-bam-api-1-core

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@nh13

nh13 commented Apr 15, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

1 similar comment
@nh13

nh13 commented Apr 15, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 15, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@nh13

nh13 commented Apr 15, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 15, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
crates/fgumi-raw-bam/tests/noodles_roundtrip.rs (1)

44-64: Strengthen the noodles-side assertions.

These properties are weaker than they look. set_cigar_ops_roundtrips only checks op count, and set_sequence_and_qualities_roundtrips only checks that noodles parses at all. A bug that preserves parseability but changes op values / decoded bases / quals still passes. Compare the noodles-decoded contents to cigar_ops, bases, and quals directly.

Also applies to: 67-92

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/fgumi-raw-bam/tests/noodles_roundtrip.rs` around lines 44 - 64, The
tests set_cigar_ops_roundtrips and set_sequence_and_qualities_roundtrips only
assert parseability or op count; update them to assert full content equality
between what our record produces and what noodles decodes: after calling
decode_with_noodles(...) on the record built by build_record(...) and modified
via set_cigar_ops / set_sequence_and_qualities, iterate the noodles-decoded
CIGAR (buf.cigar()) and compare each decoded op (length and op code) against
cigar_ops (or rec.cigar_ops_vec()), compare the decoded sequence/bases bytes
against the original sequence used to build the record, and compare the decoded
quality scores against the original quals vector; update both test functions to
use proptest::prop_assert_eq! (or appropriate proptest assertions) for these
element-wise comparisons so mismatched op values, bases, or quals fail the
tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/fgumi-raw-bam/src/raw_bam_record.rs`:
- Around line 626-649: In set_read_name, reject any embedded NULs in the
provided new_name before splicing: check new_name for a 0 byte (e.g., if
new_name.contains(&0)) and panic with a clear message like "read name contains
embedded NUL" (consistent with the existing panic behavior for length); perform
this validation before computing new_l/new_l_u8 and performing the splice so you
never write an interior NUL into the record. Ensure you still include the
trailing NUL when constructing the replacement and update self.0[8] as before.
- Around line 192-199: tags_mut() can panic when aux_data_offset_from_record
returns an offset greater than the buffer length; clamp the computed offset to
the buffer length before slicing. Change the logic in tags_mut to compute len =
self.0.len(), compute off = aux_data_offset_from_record(&self.0).unwrap_or(len)
and then set off = off.min(len) (or use a conditional check) before calling
RawTagsMut::new(&mut self.0[off..]); reference functions/values: tags_mut,
aux_data_offset_from_record, RawTagsMut::new, and self.0.

In `@crates/fgumi-raw-bam/src/tags.rs`:
- Around line 1206-1215: set_float_in_place currently writes four bytes without
validating that the aux buffer contains enough data, causing a panic on
truncated aux data; update set_float_in_place (the function shown) to perform
the same bounds check as find_float_tag/RawTagsEditor::update_float by verifying
p + 7 <= self.0.len() (or otherwise that the slice range p+3..p+7 is in-bounds)
before calling copy_from_slice, and return false if the check fails.
- Around line 1163-1200: In set_int_in_place, there are unsafe slice writes
without ensuring the buffer has enough bytes; before each match arm that writes
multi-byte values (for tag types 's'/'S' check that p + 5 <= self.0.len(), for
'i'/'I' check that p + 7 <= self.0.len(), and for single-byte 'c'/'C' check p +
4 <= self.0.len()), return false if the check fails; perform these bounds checks
immediately after finding the tag (using the p from find_tag_position) or at the
start of each corresponding match arm so the subsequent copy_from_slice or index
assignment cannot panic. Ensure you reference set_int_in_place and the match on
val_type when implementing these guards.

---

Nitpick comments:
In `@crates/fgumi-raw-bam/tests/noodles_roundtrip.rs`:
- Around line 44-64: The tests set_cigar_ops_roundtrips and
set_sequence_and_qualities_roundtrips only assert parseability or op count;
update them to assert full content equality between what our record produces and
what noodles decodes: after calling decode_with_noodles(...) on the record built
by build_record(...) and modified via set_cigar_ops /
set_sequence_and_qualities, iterate the noodles-decoded CIGAR (buf.cigar()) and
compare each decoded op (length and op code) against cigar_ops (or
rec.cigar_ops_vec()), compare the decoded sequence/bases bytes against the
original sequence used to build the record, and compare the decoded quality
scores against the original quals vector; update both test functions to use
proptest::prop_assert_eq! (or appropriate proptest assertions) for these
element-wise comparisons so mismatched op values, bases, or quals fail the
tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1ef17ff7-14b1-4cfe-ac93-4aad1631478c

📥 Commits

Reviewing files that changed from the base of the PR and between 3854d5c and 4eeb0d4.

📒 Files selected for processing (6)
  • crates/fgumi-raw-bam/src/cigar.rs
  • crates/fgumi-raw-bam/src/fields.rs
  • crates/fgumi-raw-bam/src/raw_bam_record.rs
  • crates/fgumi-raw-bam/src/sequence.rs
  • crates/fgumi-raw-bam/src/tags.rs
  • crates/fgumi-raw-bam/tests/noodles_roundtrip.rs

Comment thread crates/fgumi-raw-bam/src/raw_bam_record.rs
Comment thread crates/fgumi-raw-bam/src/raw_bam_record.rs
Comment thread crates/fgumi-raw-bam/src/tags.rs
Comment thread crates/fgumi-raw-bam/src/tags.rs
nh13 added 8 commits April 15, 2026 17:12
Closes the issue 272 Easy gap "update_array_tag_element_*". Adds the full
u8/u16/i16/i32/f32 family of in-place B-array element writers. Type-checked
against the array's sub-type byte; no-op on mismatch or out-of-range index.

Refs #272
Editor borrows the full record Vec plus a cached aux_offset, amortizing the
header scan across all tag ops. view() returns a RawTagsView over the cached
sub-slice. Subsequent commits add update/append/remove methods.

Refs #272
Closes the remaining Easy gaps from #272. update_float overwrites in place
when the tag exists as 'f', else removes and re-appends. update_array_*
covers u8/u16/i16/i32/f32: same-length overwrite is in-place, length change
splices via remove + append. Adds append_u16_array_tag and
append_f32_array_tag helpers.

Refs #272
Adds view/view_mut/tags/tags_mut/tags_editor accessors plus inherent #[inline]
delegators for every read accessor and fixed-length setter, so callers can
write rec.flags()/rec.set_pos(0)/rec.tags().find_*() directly.

Refs #272
nh13 added 2 commits April 15, 2026 17:15
Add integration tests in `crates/fgumi-raw-bam/tests/noodles_roundtrip.rs`
(requires `feature = "noodles"`) that cross-check the three length-changing
edits — `set_read_name`, `set_cigar_ops`, and `set_sequence_and_qualities` —
against noodles decoding.  Each proptest verifies that the internal field view
and the bytes noodles parses agree, giving structural confidence that the
splice logic is byte-level correct.
@nh13
nh13 force-pushed the nh/raw-bam-api-1-core branch from 4eeb0d4 to e416550 Compare April 16, 2026 00:18
@nh13
nh13 temporarily deployed to github-actions April 16, 2026 00:18 — with GitHub Actions Inactive
@nh13 nh13 added the raw-bam label Apr 16, 2026
@nh13
nh13 merged commit e88b3e8 into main Apr 16, 2026
9 checks passed
@nh13
nh13 deleted the nh/raw-bam-api-1-core branch April 16, 2026 00:25

This branch was previously deployed

1 inactive deployment
github-actions — e4165509 Deployed Apr 16, 2026 by nh13 via coverage #1154
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Complete the raw-byte BAM API in fgumi-raw-bam

1 participant