Skip to content

feat(diagnostics): Improve error diagnostics usability#47

Merged
jiacai2050 merged 2 commits intomainfrom
chore-diagnose
Mar 8, 2026
Merged

feat(diagnostics): Improve error diagnostics usability#47
jiacai2050 merged 2 commits intomainfrom
chore-diagnose

Conversation

@jiacai2050
Copy link
Owner

Add a human-readable error message retrieval method to the Diagnostics struct, making it easier to log and understand failures from curl operations. Refactor Multi to always include diagnostics and to return detailed error information for each operation. Update examples and documentation to illustrate the new diagnostics workflow.

Add a human-readable error message retrieval method to the Diagnostics
struct, making it easier to log and understand failures from curl
operations. Refactor Multi to always include diagnostics and to return
detailed error information for each operation. Update examples and
documentation to illustrate the new diagnostics workflow.
Copilot AI review requested due to automatic review settings March 8, 2026 03:52
@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances error diagnostics within the zig-curl library. It introduces a new capability to retrieve human-readable error messages directly from the Diagnostics struct and integrates this diagnostic functionality directly into the Multi handle. These changes streamline error handling, making it much easier for developers to understand and log failures from curl operations without needing to manually interpret error codes.

Highlights

  • Improved Error Messaging: A getMessage() method was added to the Diagnostics struct, allowing users to retrieve human-readable error strings for curl and multi operations.
  • Integrated Multi Diagnostics: The Multi struct was refactored to internally manage its own Diagnostics instance, removing the need for users to pass one explicitly during initialization and ensuring detailed error information is always available.
  • Updated Examples and Documentation: The README.org and example files (examples/header.zig, examples/multi.zig) were updated to reflect the new diagnostics workflow, demonstrating how to use the getMessage() method.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • README.org
    • Updated the LASTMOD timestamp.
    • Added a new "Diagnostics" section explaining how to use the diagnostics field and getMessage() for detailed error information.
  • examples/header.zig
    • Changed the type of the diagnostics variable from Easy.Diagnostics to curl.Diagnostics.
  • examples/multi.zig
    • Removed the explicit declaration of a Multi.Diagnostics variable.
    • Modified Multi.init() call to no longer require a diagnostics parameter.
    • Updated the multi.deinit() defer statement to include error handling.
    • Updated calls to checkCode to reference multi.diagnostics directly.
  • src/Multi.zig
    • Changed the diagnostics field from an optional pointer (?*Diagnostics) to a direct Diagnostics struct.
    • Modified init function to no longer accept a diagnostics parameter and to initialize the internal diagnostics field.
    • Updated deinit function to return !void and perform curl_multi_cleanup with diagnostics.
    • Changed method self parameters from Self to *Self for addHandle, removeHandle, perform, poll, wakeup, and readInfo.
    • Updated all internal calls to checkMCode to pass a pointer to the internal diagnostics field (&self.diagnostics).
  • src/errors.zig
    • Added a getMessage() public function to the Diagnostics struct, which returns a human-readable error string based on the stored CURLcode or CURLMcode.
    • Removed std.log.debug calls that printed error codes and messages from checkCode and checkMCode functions.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

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

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 significantly improves the diagnostic capabilities and usability of zig-curl. The introduction of Diagnostics.getMessage provides a clean way to retrieve human-readable error messages. The refactoring of the Multi type to manage its own diagnostics, fix a resource leak in deinit, and use pointer receivers for its methods makes the API more robust and easier to use correctly. My review includes one high-severity suggestion to address a thread-safety issue in the new getMessage function, which stems from the underlying curl_easy_strerror C function.

Comment on lines +44 to +50
pub fn getMessage(self: Diagnostics) ?[]const u8 {
const error_code = self.error_code orelse return null;
return switch (error_code) {
.code => |code| std.mem.span(c.curl_easy_strerror(code)),
.m_code => |m_code| std.mem.span(c.curl_multi_strerror(m_code)),
};
}

Choose a reason for hiding this comment

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

high

The curl_easy_strerror function is not thread-safe as it uses a global static buffer. This can lead to data races and incorrect error messages when getMessage is called from multiple threads concurrently. curl_multi_strerror is thread-safe, so it's only the .code branch that is problematic.

To fix this without a breaking API change, you can copy the error message from curl_easy_strerror into a thread-local buffer. This makes the function thread-safe. The returned slice is valid until the next call to getMessage for a CURLcode error on the same thread, which should be documented.

You'll need to add a thread-local buffer at the file scope:

threadlocal var easy_error_buffer: [c.CURL_ERROR_SIZE]u8 = undefined;
    pub fn getMessage(self: Diagnostics) ?[]const u8 {
        const error_code = self.error_code orelse return null;
        return switch (error_code) {
            .code => |code| {
                // curl_easy_strerror is not thread-safe, so we copy to a thread-local buffer.
                const msg = std.mem.span(c.curl_easy_strerror(code));
                const len = @min(msg.len, easy_error_buffer.len);
                @memcpy(easy_error_buffer[0..len], msg[0..len]);
                return easy_error_buffer[0..len];
            },
            .m_code => |m_code| std.mem.span(c.curl_multi_strerror(m_code)),
        };
    }

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR improves diagnostics ergonomics for curl operations by adding a human-readable error message accessor to Diagnostics and refactoring Multi to always retain diagnostics internally, with updated examples/docs showing the intended workflow.

Changes:

  • Add Diagnostics.getMessage() to retrieve a readable libcurl error string from stored error codes.
  • Refactor Multi to embed Diagnostics (non-optional), update method receivers to *Self, and route multi errors through &self.diagnostics.
  • Update examples and README to demonstrate using stored diagnostics for error reporting.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/errors.zig Adds Diagnostics.getMessage() and removes debug logging from checkCode/checkMCode.
src/Multi.zig Stores Diagnostics by value and passes it through multi operations; changes API shape (init/deinit/receivers).
examples/multi.zig Updates example to new Multi.init() and uses multi.diagnostics for error context.
examples/header.zig Switches diagnostics type reference to curl.Diagnostics.
README.org Documents the new diagnostics workflow via getMessage().

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +43 to +50
/// Returns a human-readable error message based on the error code.
pub fn getMessage(self: Diagnostics) ?[]const u8 {
const error_code = self.error_code orelse return null;
return switch (error_code) {
.code => |code| std.mem.span(c.curl_easy_strerror(code)),
.m_code => |m_code| std.mem.span(c.curl_multi_strerror(m_code)),
};
}
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

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

Diagnostics.getMessage() is a new public API but there’s no unit test covering its behavior (null when error_code is unset; correct mapping for both .code and .m_code). Adding a small test that compares getMessage() to curl_easy_strerror/curl_multi_strerror for a known non-OK code would help prevent regressions.

Copilot uses AI. Check for mistakes.
Comment on lines +35 to +36
pub fn deinit(self: *Self) !void {
return checkMCode(c.curl_multi_cleanup(self.multi), &self.diagnostics);
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

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

deinit now returns !void, which makes common defer multi.deinit(); usage awkward and is inconsistent with Easy.deinit()/MultiPart.deinit() being infallible. Consider keeping deinit(self: *Self) void (always call curl_multi_cleanup) and either ignore the CURLMcode, assert in debug builds, or provide a separate fallible cleanup()/tryDeinit() if callers need to surface cleanup errors via diagnostics.

Suggested change
pub fn deinit(self: *Self) !void {
return checkMCode(c.curl_multi_cleanup(self.multi), &self.diagnostics);
pub fn deinit(self: *Self) void {
// Always attempt cleanup; record diagnostics but do not propagate an error.
checkMCode(c.curl_multi_cleanup(self.multi), &self.diagnostics) catch {};

Copilot uses AI. Check for mistakes.
When a curl operation fails, it returns a generic =error.Curl=. To get more detailed information, you can use the =diagnostics= field in =Easy= or =Multi=.

#+begin_src zig
var easy = try curl.Easy.init(.{ ... });
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

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

The README snippet uses curl.Easy.init(.{ ... }), but ... is not valid Zig syntax inside a struct literal, so readers copying this example will get a compile error. Consider using a valid minimal init (e.g. .{} ) or a concrete option like . { .ca_bundle = ca_bundle } to keep the documentation runnable.

Suggested change
var easy = try curl.Easy.init(.{ ... });
var easy = try curl.Easy.init(.{});

Copilot uses AI. Check for mistakes.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@jiacai2050 jiacai2050 merged commit 9e7839d into main Mar 8, 2026
4 checks passed
@jiacai2050 jiacai2050 deleted the chore-diagnose branch March 8, 2026 04:04
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.

2 participants