Skip to content

Conversation

@lalinsky
Copy link
Owner

@lalinsky lalinsky commented Sep 3, 2025

Summary

Replaces custom JetStream error handling with comprehensive error mapping based on official NATS server error definitions. This provides precise error discrimination for all JetStream operations.

Changes

  • Comprehensive Error Mapping: Generated jetstream_errors.zig with all 196 JetStream error codes from NATS server definitions
  • Improved expected_* Error Handling: Now returns specific errors like StreamWrongLastSequence, StreamWrongLastMsgID instead of generic JetStreamError
  • Python Generation Script: Added scripts/generate_jetstream_errors.py to maintain error mappings from official NATS server sources
  • API Improvements: Exposed PublishOptions and JetStreamError in public API
  • Test Updates: All tests updated to expect specific error types for better error handling validation

Technical Details

  • Removed custom JetStreamPublishError enum in favor of comprehensive JetStreamError
  • Error mapping handles current server behavior (NATS 2.10.29) and future compatibility (error code 10164)
  • All 80 tests pass with new error handling
  • Error codes mapped from _refs/nats-server/server/errors.json

Error Code Mappings

  • 10070: StreamWrongLastMsgID - for expected_last_msg_id validation
  • 10071: StreamWrongLastSequence - for expected_last_seq and expected_last_subject_seq validation
  • 10164: StreamWrongLastSequenceConstant - future compatibility
  • Plus 193 other JetStream error codes with specific types

Test Plan

[✅] All existing tests pass with updated error expectations
[✅] Error handling works correctly for expected_* publish options
[✅] Comprehensive error mapping covers all JetStream operations

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 3, 2025

Walkthrough

Introduces a script to generate JetStream error mappings from NATS errors.json, adds the generated Zig module with error set and code-to-error mapping, updates jetstream.zig to use centralized mapping and removes JetStreamPublishError, exports JetStreamError via root, and adjusts tests to assert specific JetStreamError variants.

Changes

Cohort / File(s) Summary
Generation tooling
scripts/generate_jetstream_errors.py
New script that reads errors.json, filters JetStream errors, derives unique variant names, and generates Zig code plus a brief summary output. Exposes constant_to_error_name and main.
Generated JetStream errors module
src/jetstream_errors.zig
New generated file defining pub error set JetStreamError and pub fn mapErrorCode(u32) -> JetStreamError with a switch over codes 10002–10197; defaults to UnknownError.
JetStream core changes
src/jetstream.zig
Imports jetstream_errors, replaces local error handling to use mapErrorCode, removes pub const JetStreamPublishError, updates returned errors in publish path (e.g., NoStreamResponse, InvalidJSAck).
Root exports
src/root.zig
Exports JetStreamError from jetstream_errors.zig and PublishOptions from jetstream.zig.
Tests
tests/jetstream_msg_test.zig, tests/jetstream_test.zig
Update assertions to specific nats.JetStreamError variants; add test for expected_last_subject_seq and assert StreamWrongLastSequence.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/jetstream-comprehensive-error-handling

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

- Replace custom JetStreamPublishError with comprehensive error mapping
- Add jetstream_errors.zig generated from NATS server error definitions
- Create scripts/generate_jetstream_errors.py for error code generation
- Update all 196 JetStream error codes with proper Zig error types
- Fix expected_* option error handling to use specific error types
- Update tests to expect specific errors (StreamWrongLastSequence, NoMessageFound, etc.)
- Expose PublishOptions and JetStreamError in public API

The implementation now provides precise error discrimination for all JetStream
operations, making it easier for users to handle specific error conditions.
All tests pass with the new comprehensive error handling.
@lalinsky lalinsky force-pushed the feature/jetstream-comprehensive-error-handling branch from dfed3d2 to bdd750a Compare September 3, 2025 06:59
Copy link
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: 0

🧹 Nitpick comments (9)
src/jetstream_errors.zig (2)

4-5: Drop unused std import (adjust generator).

std isn’t used here. It’s harmless in practice, but we can avoid “unused import” churn and keep the generated file lean. Since this file is generated, please update the generator to omit this line.

Apply (for current file only; see script fix in a separate comment):

-const std = @import("std");
-

404-605: Optional: add reverse mapping helper (error -> code).

Having a generated helper like codeFor(err: JetStreamError) u32 can simplify telemetry and assertions without duplicating switches in callers.

Example addition the generator could emit:

pub fn codeFor(err: JetStreamError) u32 {
    return switch (err) {
        // mirror of mapErrorCode, e.g.:
        .StreamWrongLastSequence => 10071,
        // ...
        else => 0,
    };
}
scripts/generate_jetstream_errors.py (4)

1-1: Make the script executable or drop the shebang.

Either set the executable bit in the repo (preferred) or remove the shebang to silence EXE001.

To make it executable in git:

  • git update-index --chmod=+x scripts/generate_jetstream_errors.py

50-58: Don’t emit an unused std import into the generated Zig.

The generated file doesn’t use std; drop it from zig_content to keep output minimal and avoid potential “unused constant” warnings on some Zig versions.

-    zig_content = [
-        f"// Generated from {errors_file}",
-        "// DO NOT EDIT MANUALLY - regenerate using scripts/generate_jetstream_errors.py",
-        "",
-        "const std = @import(\"std\");",
-        "",
-        "/// JetStream error codes and their corresponding Zig error types",
-        "pub const JetStreamError = error{",
-    ]
+    zig_content = [
+        f"// Generated from {errors_file}",
+        "// DO NOT EDIT MANUALLY - regenerate using scripts/generate_jetstream_errors.py",
+        "",
+        "/// JetStream error codes and their corresponding Zig error types",
+        "pub const JetStreamError = error{",
+    ]

7-10: Optional: auto-format the generated Zig via zig fmt (if available).

Keeps us aligned with “zig fmt before commit” without relying on manual steps.

-import json
-import sys
-from pathlib import Path
+import json
+import sys
+import shutil
+import subprocess
+from pathlib import Path
     with open(output_file, 'w') as f:
         f.write('\n'.join(zig_content))
 
     print(f"Generated {output_file} with {len(js_errors)} JetStream error codes")
+
+    # Format with zig fmt if available
+    zig_path = shutil.which("zig")
+    if zig_path:
+        try:
+            subprocess.run([zig_path, "fmt", str(output_file)], check=True)
+        except Exception as e:
+            print(f"Warning: zig fmt failed: {e}")

122-122: Fix Ruff F541: remove unnecessary f-string.

-print(f"Error breakdown by HTTP status code:")
+print("Error breakdown by HTTP status code:")
tests/jetstream_test.zig (1)

763-798: Add coverage for expected_last_msg_id → StreamWrongLastMsgID (10070).

You’re covering 10071 well. A quick test using .{ .expected_last_msg_id = "some-id" } with a mismatched last ID would validate 10070 end-to-end.

Sketch:

test "JetStream publish with expected last msg id mismatch" {
    const conn = try utils.createDefaultConnection();
    defer utils.closeConnection(conn);
    var js = conn.jetstream(.{});
    defer js.deinit();

    const stream = try utils.generateUniqueStreamName(testing.allocator);
    defer testing.allocator.free(stream);
    const subject = try utils.generateSubjectFromStreamName(testing.allocator, stream);
    defer testing.allocator.free(subject);

    var _ = try js.addStream(.{ .name = stream, .subjects = &.{subject} });

    // Publish one message with a known msg_id
    _ = try js.publish(subject, "first", .{ .msg_id = "id-1" });

    // Mismatch expected_last_msg_id
    const res = js.publish(subject, "second", .{ .expected_last_msg_id = "id-other" });
    try testing.expectError(nats.JetStreamError.StreamWrongLastMsgID, res);
}
src/jetstream.zig (2)

1196-1197: Publish: map NoResponders → NoStreamResponse — good; consider centralizing in sendRequest().

This is the right UX for publish. For consistency across all JetStream API calls, consider doing the same mapping in sendRequest() so INFO/STREAM/CONSUMER calls get the same error when JetStream is unavailable:

fn sendRequest(self: *JetStream, subject: []const u8, payload: []const u8) !*Message {
    const full_subject = try std.fmt.allocPrint(self.allocator, "{s}{s}", .{ default_api_prefix, subject });
    defer self.allocator.free(full_subject);

    return self.nc.request(full_subject, payload, self.opts.request_timeout_ms) catch |e| {
        if (e == error.NoResponders) return error.NoStreamResponse;
        return e;
    };
}

602-604: Map server err_code to concrete errors—verify fallback and consider larger parse buffer.

  • Verify that mapErrorCode(info.err_code) covers unknown or zero codes, falling back to a generic JetStreamError if needed.
  • For long error descriptions, bump the 1024-byte parse buffer (near Line 590) to at least 2048 bytes (or 4096) to avoid silent parse failures.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ab3ce62 and bdd750a.

📒 Files selected for processing (6)
  • scripts/generate_jetstream_errors.py (1 hunks)
  • src/jetstream.zig (3 hunks)
  • src/jetstream_errors.zig (1 hunks)
  • src/root.zig (1 hunks)
  • tests/jetstream_msg_test.zig (4 hunks)
  • tests/jetstream_test.zig (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.zig

📄 CodeRabbit inference engine (CLAUDE.md)

Format code using zig fmt before commit

Files:

  • src/root.zig
  • tests/jetstream_test.zig
  • src/jetstream_errors.zig
  • tests/jetstream_msg_test.zig
  • src/jetstream.zig
🧠 Learnings (2)
📓 Common learnings
Learnt from: lalinsky
PR: lalinsky/nats.zig#31
File: tests/jetstream_pull_test.zig:21-23
Timestamp: 2025-08-25T14:51:33.570Z
Learning: The nats.zig project uses a "tests:beforeEach" test in tests/all_tests.zig that runs before each test to clean up all JetStream streams and consumers, ensuring test isolation. This eliminates the need for unique test resource names as each test starts with a clean JetStream state.
📚 Learning: 2025-08-25T14:51:33.570Z
Learnt from: lalinsky
PR: lalinsky/nats.zig#31
File: tests/jetstream_pull_test.zig:21-23
Timestamp: 2025-08-25T14:51:33.570Z
Learning: The nats.zig project uses a "tests:beforeEach" test in tests/all_tests.zig that runs before each test to clean up all JetStream streams and consumers, ensuring test isolation. This eliminates the need for unique test resource names as each test starts with a clean JetStream state.

Applied to files:

  • tests/jetstream_test.zig
  • tests/jetstream_msg_test.zig
🪛 Ruff (0.12.2)
scripts/generate_jetstream_errors.py

1-1: Shebang is present but file is not executable

(EXE001)


122-122: f-string without any placeholders

Remove extraneous f prefix

(F541)

🔇 Additional comments (6)
src/jetstream_errors.zig (1)

145-148: Good: precise mapping for 10070, 10071, 10164.

StreamWrongLastMsgID (10070), StreamWrongLastSequence (10071), and StreamWrongLastSequenceConstant (10164) are distinctly represented and mapped. This matches the server behavior tests care about.

Also applies to: 333-334, 475-477, 569-569

src/root.zig (1)

35-36: Re-exporting JetStreamError and PublishOptions looks right.

This matches the new centralized error mapping and keeps the public API coherent.

tests/jetstream_msg_test.zig (1)

123-124: Good: assert specific JetStreamError variants.

Asserting NoMessageFound, StreamNotFound, and StreamMsgDeleteFailed tightens the contract and validates the mapping end-to-end. Given your beforeEach cleanup, the fixed stream names are fine.

Also applies to: 167-168, 227-228, 239-240, 243-244, 247-248

tests/jetstream_test.zig (1)

758-761: Good: expect StreamWrongLastSequence for bad expected_last_seq.

This nails the 10071 path.

src/jetstream.zig (2)

41-41: Import of generated JetStream error mapping — good.

This correctly wires the file to the centralized, generated error set.


1204-1205: Publish: invalid PubAck JSON → InvalidJSAck — good.

Matches the new error model and keeps publish errors specific.

@lalinsky lalinsky merged commit 20c986c into main Sep 3, 2025
4 checks passed
@lalinsky lalinsky deleted the feature/jetstream-comprehensive-error-handling branch September 3, 2025 18:22
@coderabbitai coderabbitai bot mentioned this pull request Sep 8, 2025
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