Skip to content

💥 Replace wire-scalar enums with byte-backed newtypes#3218

Merged
ChanTsune merged 4 commits into
mainfrom
refactor/wire-scalar-newtype
Jul 18, 2026
Merged

💥 Replace wire-scalar enums with byte-backed newtypes#3218
ChanTsune merged 4 commits into
mainfrom
refactor/wire-scalar-newtype

Conversation

@ChanTsune

@ChanTsune ChanTsune commented Jul 17, 2026

Copy link
Copy Markdown
Owner

Summary

Replace the DataKind, Compression, Encryption, and CipherMode enums with byte-backed newtypes (pub struct X(u8) + associated constants). The open-enum shape allowed constructing aliased values such as DataKind::Reserved(0) (same wire byte as File but unequal), and assigning a new known value would silently change the meaning of existing Reserved(n) matches; with the newtype every byte value has a single canonical representation and future assignments are non-breaking.

Notes

  • Canonical constants are SCREAMING_SNAKE (DataKind::FILE, …). Old variant spellings remain available as #[deprecated] constants where the spelling changed; CipherMode::CBC/CTR and Compression::XZ are unchanged.
  • TryFrom<u8> is kept (behavior unchanged, still infallible) and shadowed by a deprecated inherent try_from, so X::try_from(b) call sites get a deprecation warning; both will be removed together in a future release. from_byte is the successor.
  • Reserved(v)/Private(v) pattern matches no longer compile; use a wildcard arm with is_reserved()/is_private().
  • Debug output is unchanged. Ord now follows the byte value; the only observable difference is Compression's unassigned byte 3 now ordering before XZ (4).
  • Considered and rejected constructors: new_reserved (with "unassigned → Some" semantics its result would silently change across versions once a value is assigned; with "any value < 128 → Some" semantics new_reserved(0) would return FILE — the reserved range is the specification's namespace) and unsafe new_unchecked (every byte is a valid canonical value, so there is no safety invariant for unsafe to guard; unchecked total construction is exactly from_byte).

Summary by CodeRabbit

  • New Features

    • Standardized archive metadata handling for compression, encryption, cipher modes, and entry types.
    • Added support for constructing and inspecting custom and unknown metadata values.
  • Bug Fixes

    • Improved archive header validation and handling of unsupported metadata values.
    • Preserved existing compression, encryption, extraction, comparison, and verification behavior across CLI and library workflows.
  • Documentation

    • Updated examples and API documentation to reflect the standardized metadata options.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The PR converts archive metadata enums to byte-backed types with uppercase constants and compatibility helpers, updates header and reader handling, migrates CLI paths, and revises tests, fuzz targets, benchmarks, examples, and documentation.

Changes

Archive metadata migration

Layer / File(s) Summary
Byte-backed option types
lib/src/entry/options.rs
Compression, Encryption, CipherMode, and DataKind now wrap raw bytes, expose conversion/classification helpers, and retain deprecated aliases.
Header and reader integration
lib/src/entry/header.rs, lib/src/entry/content.rs, lib/src/entry/read.rs, lib/src/entry/builder.rs
Header construction, deserialization, content classification, and crypto/compression dispatch use the new constants and byte constructors.
CLI metadata dispatch
cli/src/..., xtask/src/main.rs
CLI extraction, comparison, listing, verification, permission, and write-option logic uses the renamed metadata constants.
Tests and supporting integrations
cli/tests/..., lib/tests/..., fuzz/fuzz_targets/*, lib/benches/*, lib/examples/*
Tests, fuzz targets, benchmarks, examples, compatibility checks, and documentation are updated for the new APIs.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested labels: cli, lib, break

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: replacing wire-scalar enums with byte-backed newtypes.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/wire-scalar-newtype

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.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the Compression, Encryption, CipherMode, and DataKind types from enums to struct-based representations wrapping a u8. It introduces uppercase constants (e.g., Compression::NO, Encryption::AES, DataKind::FILE) to replace the deprecated CamelCase variants, updates the codebase to use these new constants, and simplifies parsing logic using infallible byte conversions. Additionally, comprehensive unit tests are added to verify the new struct-based representations and ensure backward compatibility. I have no feedback to provide as there are no review comments.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@ChanTsune
ChanTsune force-pushed the refactor/wire-scalar-newtype branch from 8ef55b4 to da30e15 Compare July 17, 2026 08:57
@github-actions github-actions Bot added cli This issue is about cli application lib This issue is about lib crate break API braking change labels Jul 17, 2026
@ChanTsune
ChanTsune force-pushed the refactor/wire-scalar-newtype branch from da30e15 to 26f55aa Compare July 18, 2026 00:20

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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)
cli/src/command/extract.rs (1)

1314-1319: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Gracefully handle unknown DataKinds to prevent denial-of-service.

Crafted archives containing a private or reserved DataKind (e.g., a raw byte value >= 4) will bypass the SYMBOLIC_LINK and DIRECTORY checks in the upstream extraction loops and fall through to extract_file_entry. This triggers the unreachable! macro, panicking the thread and aborting extraction.

Replace the panic with a graceful warning and early return to handle unknown byte values safely.

🛡️ Proposed fix
-    if item.header().data_kind() != DataKind::FILE {
-        unreachable!(
-            "extract_file_entry called with {:?}",
-            item.header().data_kind()
-        );
-    }
+    if item.header().data_kind() != DataKind::FILE {
+        log::warn!(
+            "Skipping unsupported data kind for {}: {:?}",
+            item_path,
+            item.header().data_kind()
+        );
+        return Ok(());
+    }
🤖 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 `@cli/src/command/extract.rs` around lines 1314 - 1319, Update
extract_file_entry so unexpected DataKind values no longer trigger unreachable!
or panic. Log a warning containing the received kind, then return early;
preserve normal file extraction for DataKind::FILE and existing handling for
symbolic links and directories.
🧹 Nitpick comments (2)
lib/src/entry/options.rs (1)

1561-1567: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Lock the wire-byte ordering into a regression test.

Derived Ord currently satisfies the requirement, but no test protects reserved byte 3 ordering before XZ (4).

Proposed test
+    #[test]
+    fn compression_orders_by_wire_byte() {
+        let reserved_three = Compression::from_byte(3);
+        assert!(Compression::ZSTANDARD < reserved_three);
+        assert!(reserved_three < Compression::XZ);
+    }
🤖 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 `@lib/src/entry/options.rs` around lines 1561 - 1567, Extend
compression_known_constants_map_to_spec_bytes to assert the ordering around the
reserved byte: verify the value corresponding to reserved byte 3 sorts before
Compression::XZ (byte 4). Preserve the existing assertions for the known
constants and use the enum’s ordering behavior directly.
cli/src/command/diff.rs (1)

319-345: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove redundant match arms.

Since DataKind is now a byte-backed newtype, exhaustive matching is safely handled by the _ => wildcard arm. The arms for DataKind::FILE | DataKind::DIRECTORY | DataKind::SYMBOLIC_LINK and the fallback DataKind::HARD_LINK perform the exact same action as the wildcard arm and can be cleanly removed to simplify the logic.

♻️ Proposed refactor
-        DataKind::FILE | DataKind::DIRECTORY | DataKind::SYMBOLIC_LINK => {
-            println!("{}", DiffKind::TypeMismatch.display(path_str));
-        }
         DataKind::HARD_LINK if meta.is_file() => {
             let EntryContent::HardLink(stored) = entry.content(read_options)? else {
                 unreachable!("data_kind() returned HardLink");
             };
             match is_same_file(path, stored.as_str()) {
                 Ok(true) => (),
                 Ok(false) => {
                     println!(
                         "{}",
                         DiffKind::NotLinked(stored.to_string()).display(path_str)
                     );
                 }
                 Err(e) if e.kind() == io::ErrorKind::NotFound => {
                     println!("{}", DiffKind::Missing.display(path_str));
                 }
                 Err(e) => return Err(e),
             }
         }
-        DataKind::HARD_LINK => {
-            println!("{}", DiffKind::TypeMismatch.display(path_str));
-        }
         _ => {
             println!("{}", DiffKind::TypeMismatch.display(path_str));
         }
🤖 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 `@cli/src/command/diff.rs` around lines 319 - 345, In the DataKind match within
the diff handling logic, remove the redundant DataKind::FILE |
DataKind::DIRECTORY | DataKind::SYMBOLIC_LINK arm and the fallback
DataKind::HARD_LINK arm. Retain the specialized DataKind::HARD_LINK if
meta.is_file() handling and the existing _ wildcard arm for all remaining cases.
🤖 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 `@cli/src/command/extract.rs`:
- Around line 1314-1319: Update extract_file_entry so unexpected DataKind values
no longer trigger unreachable! or panic. Log a warning containing the received
kind, then return early; preserve normal file extraction for DataKind::FILE and
existing handling for symbolic links and directories.

---

Nitpick comments:
In `@cli/src/command/diff.rs`:
- Around line 319-345: In the DataKind match within the diff handling logic,
remove the redundant DataKind::FILE | DataKind::DIRECTORY |
DataKind::SYMBOLIC_LINK arm and the fallback DataKind::HARD_LINK arm. Retain the
specialized DataKind::HARD_LINK if meta.is_file() handling and the existing _
wildcard arm for all remaining cases.

In `@lib/src/entry/options.rs`:
- Around line 1561-1567: Extend compression_known_constants_map_to_spec_bytes to
assert the ordering around the reserved byte: verify the value corresponding to
reserved byte 3 sorts before Compression::XZ (byte 4). Preserve the existing
assertions for the known constants and use the enum’s ordering behavior
directly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 02e0fa17-8092-4bf3-aa41-492d841b0b3a

📥 Commits

Reviewing files that changed from the base of the PR and between 063d82c and 26f55aa.

📒 Files selected for processing (37)
  • cli/src/cli.rs
  • cli/src/command/bsdtar.rs
  • cli/src/command/chmod.rs
  • cli/src/command/core.rs
  • cli/src/command/diff.rs
  • cli/src/command/extract.rs
  • cli/src/command/list.rs
  • cli/src/command/verify.rs
  • cli/tests/cli/bsdtar/option_update_strip_components.rs
  • cli/tests/cli/bsdtar/symlink.rs
  • cli/tests/cli/create/empty_entries.rs
  • cli/tests/cli/create/symlink.rs
  • cli/tests/cli/create/symlink_metadata.rs
  • cli/tests/cli/delete/directory_entry.rs
  • cli/tests/cli/delete/symlink_entry.rs
  • cli/tests/cli/extract/option_keep_permission.rs
  • cli/tests/cli/update/symlink_entry.rs
  • cli/tests/cli/utils/archive.rs
  • fuzz/fuzz_targets/aes_cbc.rs
  • fuzz/fuzz_targets/aes_ctr.rs
  • fuzz/fuzz_targets/camellia_cbc.rs
  • fuzz/fuzz_targets/camellia_ctr.rs
  • fuzz/fuzz_targets/split_archive.rs
  • lib/benches/create_extract.rs
  • lib/examples/change_compression_method.rs
  • lib/src/archive.rs
  • lib/src/entry.rs
  • lib/src/entry/builder.rs
  • lib/src/entry/content.rs
  • lib/src/entry/header.rs
  • lib/src/entry/options.rs
  • lib/src/entry/read.rs
  • lib/src/lib.rs
  • lib/tests/extract_compatibility.rs
  • lib/tests/extract_multipart_compatibility.rs
  • lib/tests/extract_solid_compatibility.rs
  • xtask/src/main.rs

@ChanTsune
ChanTsune merged commit dbba0ee into main Jul 18, 2026
113 of 114 checks passed
@ChanTsune
ChanTsune deleted the refactor/wire-scalar-newtype branch July 18, 2026 04:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

break API braking change cli This issue is about cli application lib This issue is about lib crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant