Skip to content

moq-mux: replace anyhow with thiserror#1495

Open
kixelated wants to merge 8 commits into
devfrom
claude/stupefied-chandrasekhar-da73a5
Open

moq-mux: replace anyhow with thiserror#1495
kixelated wants to merge 8 commits into
devfrom
claude/stupefied-chandrasekhar-da73a5

Conversation

@kixelated
Copy link
Copy Markdown
Collaborator

Summary

  • moq-mux is a library, so per the workspace convention (thiserror for libs, anyhow for binaries) it shouldn't be exporting anyhow types in its public API.
  • Each codec, container, and catalog format gets its own Error enum, flattened into crate::Error via #[from]: codec::{annexb,aac,opus,h264,h265,av1}::Error, container::{fmp4,mkv,hls}::Error, catalog::msf::Error.
  • Pure parsing functions (Config::parse, Sps::parse, Avcc::parse, Avc1/Hvc1::transform, etc.) return their module-local Result. Importer/exporter struct methods return crate::Result because they interleave codec errors with moq_net::Error and the other leaf errors that flow through track operations.

Downstream impact

  • libmoq::Error::{InitFailed,DecodeFailed} now wrap Arc<moq_mux::Error> instead of Arc<anyhow::Error>. Added a BufferNotConsumed variant for the one ad-hoc anyhow!() site that didn't fit either bucket.
  • moq-cli's PublishDecoder keeps anyhow::Result and bridges moq_mux::Result with explicit ?.

Test plan

  • just check (cargo check + clippy -D warnings + fmt --check + doc + shear + sort) passes
  • cargo test -p moq-mux — 141 tests pass
  • cargo check --workspace --all-targets --exclude moq-gst builds (moq-gst is blocked by a local gstreamer install only, not by these changes)

🤖 Generated with Claude Code
(Written by Claude)

moq-mux is a library crate; per the workspace convention (thiserror for
libraries, anyhow for binaries) it shouldn't be exporting anyhow types
in its public API.

Each codec, container, and catalog format module now exports its own
Error enum (codec::{annexb,aac,opus,h264,h265,av1}::Error,
container::{fmp4,mkv,hls}::Error, catalog::msf::Error), flattened into
crate::Error via #[from]. Pure parsing functions (Config::parse,
Sps::parse, Avcc::parse, Avc1/Hvc1::transform, etc.) return their
module-local Result; importer/exporter structs return crate::Result so
they can mix codec errors with moq_net::Error and the other leaf
errors that flow through track operations.

Downstream:
- libmoq::Error::{InitFailed,DecodeFailed} now wrap Arc<moq_mux::Error>
  instead of Arc<anyhow::Error>; added BufferNotConsumed for the one
  ad-hoc anyhow!() site that didn't fit either bucket.
- moq-cli's PublishDecoder bridges moq_mux::Result into anyhow::Result
  with explicit `?` since the binary still uses anyhow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 24, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 22c54336-04ea-47d0-ae53-d2336bf34d07

📥 Commits

Reviewing files that changed from the base of the PR and between 497c742 and 6540b49.

📒 Files selected for processing (3)
  • rs/moq-mux/src/codec/av1/import.rs
  • rs/moq-mux/src/codec/h264/import.rs
  • rs/moq-mux/src/codec/h265/import.rs
🚧 Files skipped from review as they are similar to previous changes (3)
  • rs/moq-mux/src/codec/h265/import.rs
  • rs/moq-mux/src/codec/h264/import.rs
  • rs/moq-mux/src/codec/av1/import.rs

Walkthrough

This PR replaces untyped anyhow errors with structured Error enums and Result aliases across the codebase. It adds module- and crate-level Error/Result types, converts public APIs and internal helpers in codecs (AAC, Opus, AV1, H.264, H.265, AnnexB), containers (fMP4, HLS, MKV), MSF catalog consumer, and import/stream dispatchers to use typed errors, introduces BufferNotConsumed/UnknownFormat variants, updates libmoq FFI mappings and moq-cli call sites, and removes the anyhow dependency.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main change: replacing anyhow with thiserror in moq-mux library.
Description check ✅ Passed The description clearly explains the motivation (workspace convention), implementation details (per-module Error enums, Result types), downstream impact, and test plan validating the changes.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch claude/stupefied-chandrasekhar-da73a5

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.

kixelated and others added 2 commits May 24, 2026 19:02
- Drop unused crate::Error::NotInitialized variant.
- Consolidate fmp4::Error::{NoMoof,MissingMoof} and {NoMoov,MissingMoov}
  duplicates; both pairs described the same condition. Tighten the
  remaining messages to match the Missing* sibling variants.
- Add Result<T> type aliases to container::{fmp4,mkv,hls} and
  catalog::msf so all per-module error enums expose one. fmp4's Wire
  Container impl falls back to std::result::Result because it needs the
  two-arg form for Self::Error.
- Reorder libmoq::Error::ReturnCode so BufferNotConsumed sits next to
  its numeric neighbors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…drasekhar-da73a5

# Conflicts:
#	rs/libmoq/src/error.rs
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
rs/moq-mux/src/container/fmp4/mod.rs (1)

192-227: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Return an explicit bounds error instead of silently truncating decoded samples.

On Line 225, decode returns Ok(frames) when sample bounds exceed mdat length. That silently accepts truncated/corrupt fragments. This should fail with Error::SampleRangeOutOfBounds.

Proposed fix
-			if end > mdat_data.len() {
-				return Ok(frames);
-			}
+			if end > mdat_data.len() {
+				return Err(Error::SampleRangeOutOfBounds {
+					start: offset,
+					end,
+					len: mdat_data.len(),
+				});
+			}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rs/moq-mux/src/container/fmp4/mod.rs` around lines 192 - 227, The decode
function currently returns Ok(frames) when a sample's end index exceeds
mdat_data length, silently truncating; change this to return
Err(Error::SampleRangeOutOfBounds) instead. In the loop inside decode (for trun
in &traf.trun { for entry in &trun.entries { ... }}), replace the early return
Ok(frames) when end > mdat_data.len() with an Err(...) using
Error::SampleRangeOutOfBounds so malformed/corrupt fragments fail rather than
being silently accepted; ensure the error is propagated from decode's
Result<Vec<Frame>> signature.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@rs/moq-mux/src/container/fmp4/mod.rs`:
- Around line 192-227: The decode function currently returns Ok(frames) when a
sample's end index exceeds mdat_data length, silently truncating; change this to
return Err(Error::SampleRangeOutOfBounds) instead. In the loop inside decode
(for trun in &traf.trun { for entry in &trun.entries { ... }}), replace the
early return Ok(frames) when end > mdat_data.len() with an Err(...) using
Error::SampleRangeOutOfBounds so malformed/corrupt fragments fail rather than
being silently accepted; ensure the error is propagated from decode's
Result<Vec<Frame>> signature.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a9a25ca6-6721-4931-9a1e-34b19c872c8f

📥 Commits

Reviewing files that changed from the base of the PR and between 42f6c50 and cd4a77a.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (28)
  • rs/libmoq/src/error.rs
  • rs/libmoq/src/publish.rs
  • rs/moq-cli/src/publish.rs
  • rs/moq-mux/Cargo.toml
  • rs/moq-mux/src/catalog/msf/consumer.rs
  • rs/moq-mux/src/catalog/msf/mod.rs
  • rs/moq-mux/src/codec/aac/import.rs
  • rs/moq-mux/src/codec/aac/mod.rs
  • rs/moq-mux/src/codec/annexb.rs
  • rs/moq-mux/src/codec/av1/import.rs
  • rs/moq-mux/src/codec/av1/mod.rs
  • rs/moq-mux/src/codec/h264/import.rs
  • rs/moq-mux/src/codec/h264/mod.rs
  • rs/moq-mux/src/codec/h265/import.rs
  • rs/moq-mux/src/codec/h265/mod.rs
  • rs/moq-mux/src/codec/opus/import.rs
  • rs/moq-mux/src/codec/opus/mod.rs
  • rs/moq-mux/src/container/fmp4/export.rs
  • rs/moq-mux/src/container/fmp4/import.rs
  • rs/moq-mux/src/container/fmp4/mod.rs
  • rs/moq-mux/src/container/hls/import.rs
  • rs/moq-mux/src/container/hls/mod.rs
  • rs/moq-mux/src/container/mkv/export.rs
  • rs/moq-mux/src/container/mkv/import.rs
  • rs/moq-mux/src/container/mkv/mod.rs
  • rs/moq-mux/src/container/source.rs
  • rs/moq-mux/src/error.rs
  • rs/moq-mux/src/import.rs
💤 Files with no reviewable changes (1)
  • rs/moq-mux/Cargo.toml

decode() previously returned Ok(frames) when a sample's end index
exceeded mdat length, silently truncating the frame list. By the time
decode() runs the full CMAF fragment is in hand, so a size mismatch is
a corrupt-publisher signal rather than a streaming-truncation case
worth tolerating. Returning Error::SampleRangeOutOfBounds lets the
caller see the failure instead of receiving a misleading partial decode.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@kixelated
Copy link
Copy Markdown
Collaborator Author

Addressed the out-of-bounds-samples finding in 4737fac: decode() now returns Error::SampleRangeOutOfBounds instead of silently truncating.

kixelated and others added 2 commits May 27, 2026 08:37
…drasekhar-da73a5

Conflicts in moq-mux Import structs where main (#1515) added a `seek`
method using anyhow::Result; reconciled to return the per-module
crate::Result/Result variant matching the rest of this branch's refactor.
The h264/h265/av1 seek variants swap `.context("not initialized")` for
`.ok_or(Error::NotInitialized)?` using each module's NotInitialized
variant.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@rs/moq-mux/src/codec/h264/import.rs`:
- Around line 462-467: The seek method currently only calls track.seek and
ignores any buffered avc3 access unit, which can cause pre-seek NALs to be
flushed into the post-seek group; update seek(&mut self, sequence: u64) in
import.rs to either (A) reject seeks while an avc3 AU is buffered by checking
self.state for State::Avc3 and that State::Avc3.current.is_some() and returning
a clear error (e.g., Error::InvalidSeekWhileBufferedAvc3) or (B) atomically
discard/reset the pending AU before calling track.seek (clear
State::Avc3.current and related per-AU state and ensure maybe_start_frame() will
not re-flush it); reference the symbols seek, State::Avc3.current,
maybe_start_frame, and track.seek when making the change so the behavior
prevents cross-boundary flushing.
🪄 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: d51c9d6d-8b80-457b-bfbc-5c26e3857532

📥 Commits

Reviewing files that changed from the base of the PR and between 4737fac and 497c742.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (9)
  • rs/moq-mux/Cargo.toml
  • rs/moq-mux/src/codec/aac/import.rs
  • rs/moq-mux/src/codec/av1/import.rs
  • rs/moq-mux/src/codec/h264/import.rs
  • rs/moq-mux/src/codec/h265/import.rs
  • rs/moq-mux/src/codec/opus/import.rs
  • rs/moq-mux/src/container/fmp4/import.rs
  • rs/moq-mux/src/container/mkv/import.rs
  • rs/moq-mux/src/import.rs
🚧 Files skipped from review as they are similar to previous changes (5)
  • rs/moq-mux/src/codec/aac/import.rs
  • rs/moq-mux/src/codec/av1/import.rs
  • rs/moq-mux/src/import.rs
  • rs/moq-mux/src/container/mkv/import.rs
  • rs/moq-mux/src/codec/h265/import.rs

Comment thread rs/moq-mux/src/codec/h264/import.rs
h264 (avc3), h265, and av1 buffer NALs/OBUs in a `current` access-unit
struct that's flushed on the next maybe_start_frame(). Previously
seek() only advanced the underlying track; pre-seek bytes survived and
would later be emitted into the post-seek group with the wrong
timestamp. Reset `current` (and h264's avc3 State) before calling
track.seek() so the discontinuity is honored on both sides.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@kixelated kixelated changed the base branch from main to dev May 28, 2026 16:37
Comment thread rs/libmoq/src/error.rs Outdated
/// Media decoder initialization failed.
#[error("init failed: {0}")]
InitFailed(Arc<anyhow::Error>),
InitFailed(Arc<moq_mux::Error>),
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Could we make the moq_mux::Error Clone so we don't need to wrap it in an Arc?

And maybe combine InitFailed and DecodeFailed into a single Mux(#[from] moq_mux::Error)? Don't worry about backwards compatibility for error codes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done in 4196dd5: every moq-mux Error now derives Clone (non-Clone leaves like std::io::Error, mp4_atom::Error, reqwest::Error, and TagWriterError are wrapped in Arc), and libmoq drops InitFailed/DecodeFailed in favor of Mux(#[from] moq_mux::Error) with plain ? at the call sites.

All moq-mux per-module Error enums now derive Clone. Non-Clone leaf
errors (mp4_atom::Error, std::io::Error, reqwest::Error,
webm_iterable TagWriterError) are wrapped in Arc with a manual
From<T> impl, mirroring the pattern hang::Error already uses for
serde_json::Error.

moq-loc::Error has only unit variants, so it gains a plain Clone
derive.

With moq_mux::Error: Clone, libmoq no longer needs to Arc-wrap it. The
InitFailed/DecodeFailed variants collapse into the existing
Mux(#[from] moq_mux::Error), and the explicit .map_err sites in
libmoq::publish disappear in favor of plain `?`. Return codes -10 and
-11 are gone; per discussion on the PR, callers are not matching on
them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@kixelated kixelated enabled auto-merge (squash) May 28, 2026 19:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant