From 1c227e645cdb516793ad1ad204da4c25bc09cb5d Mon Sep 17 00:00:00 2001 From: jiacai2050 Date: Sun, 8 Mar 2026 11:51:05 +0800 Subject: [PATCH 1/2] feat(diagnostics): Improve error diagnostics usability 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. --- README.org | 19 ++++++++++++++++++- examples/header.zig | 2 +- examples/multi.zig | 14 +++++++------- src/Multi.zig | 32 ++++++++++++++++---------------- src/errors.zig | 18 +++++++++--------- 5 files changed, 51 insertions(+), 34 deletions(-) diff --git a/README.org b/README.org index 1b0f8c7..0372a60 100644 --- a/README.org +++ b/README.org @@ -1,6 +1,6 @@ #+TITLE: zig-curl #+DATE: 2023-09-16T23:16:15+0800 -#+LASTMOD: 2025-10-17T09:10:36+0800 +#+LASTMOD: 2026-03-08T11:50:51+0800 #+OPTIONS: toc:nil num:nil #+STARTUP: content @@ -61,6 +61,23 @@ pub fn main() !void { Check [[file:examples]] for more examples. +** Diagnostics +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(.{ ... }); + defer easy.deinit(); + + easy.perform() catch |err| { + if (err == error.Curl) { + if (easy.diagnostics.getMessage()) |msg| { + std.log.err("curl failed: {s}", .{msg}); + } + } + return err; + }; +#+end_src + * Documentation See https://jiacai2050.github.io/zig-curl/ diff --git a/examples/header.zig b/examples/header.zig index 5e4a1cc..ffb47d1 100644 --- a/examples/header.zig +++ b/examples/header.zig @@ -33,7 +33,7 @@ fn iterateRedirectedHeaders(easy: *Easy) !void { try easy.setFollowLocation(true); const resp = try easy.fetch("https://edgebin.liujiacai.net/redirect/2", .{}); - var diagnostics: Easy.Diagnostics = .{}; + var diagnostics: curl.Diagnostics = .{}; const redirects = try resp.getRedirectCount(&diagnostics); try std.testing.expectEqual(redirects, 2); diff --git a/examples/multi.zig b/examples/multi.zig index 52c6be3..f587ddc 100644 --- a/examples/multi.zig +++ b/examples/multi.zig @@ -25,10 +25,10 @@ pub fn main() !void { defer if (gpa.deinit() != .ok) @panic("leak"); const allocator = gpa.allocator(); - var diagnostics: Multi.Diagnostics = .{}; - - const multi = try Multi.init(&diagnostics); - defer multi.deinit(); + var multi = try Multi.init(); + defer multi.deinit() catch |e| { + std.debug.print("multi handle deinit failed, err:{any}", .{e}); + }; var wtr1 = std.Io.Writer.Allocating.init(allocator); defer wtr1.deinit(); @@ -67,16 +67,16 @@ pub fn main() !void { } // check that the request was successful - try checkCode(info.msg.data.result, &diagnostics); + try checkCode(info.msg.data.result, &multi.diagnostics); // Read the HTTP status code var status_code: c_long = 0; - try checkCode(c.curl_easy_getinfo(easy_handle, c.CURLINFO_RESPONSE_CODE, &status_code), &diagnostics); + try checkCode(c.curl_easy_getinfo(easy_handle, c.CURLINFO_RESPONSE_CODE, &status_code), &multi.diagnostics); std.debug.print("Response Code: {any}\n", .{status_code}); // Get the private data (buffer) associated with this handle var private_data: ?*anyopaque = null; - try checkCode(c.curl_easy_getinfo(easy_handle, c.CURLINFO_PRIVATE, &private_data), &diagnostics); + try checkCode(c.curl_easy_getinfo(easy_handle, c.CURLINFO_PRIVATE, &private_data), &multi.diagnostics); const writer: *Writer = @ptrCast(@alignCast(private_data.?)); std.debug.print("Response body: {s}\n", .{writer.buffered()}); diff --git a/src/Multi.zig b/src/Multi.zig index 77519e8..8304943 100644 --- a/src/Multi.zig +++ b/src/Multi.zig @@ -19,42 +19,42 @@ pub const Diagnostics = errors.Diagnostics; const Self = @This(); multi: *c.CURLM, -diagnostics: ?*Diagnostics, +diagnostics: Diagnostics, -pub fn init(diagnostics: ?*Diagnostics) !Self { +pub fn init() !Self { const core = c.curl_multi_init(); if (core == null) { return error.InitMulti; } return .{ .multi = core.?, - .diagnostics = diagnostics, + .diagnostics = .{}, }; } -pub fn deinit(self: Self) void { - _ = self; +pub fn deinit(self: *Self) !void { + return checkMCode(c.curl_multi_cleanup(self.multi), &self.diagnostics); } /// Adds the easy handle to the multi_handle. /// https://curl.se/libcurl/c/curl_multi_add_handle.html -pub fn addHandle(self: Self, easy: *Easy) !void { +pub fn addHandle(self: *Self, easy: *Easy) !void { try easy.setCommonOpts(); - return checkMCode(c.curl_multi_add_handle(self.multi, easy.handle), self.diagnostics); + return checkMCode(c.curl_multi_add_handle(self.multi, easy.handle), &self.diagnostics); } /// Removes a given easy_handle from the multi_handle. /// https://curl.se/libcurl/c/curl_multi_remove_handle.html -pub fn removeHandle(self: Self, handle: *c.CURL) !void { - return checkMCode(c.curl_multi_remove_handle(self.multi, handle), self.diagnostics); +pub fn removeHandle(self: *Self, handle: *c.CURL) !void { + return checkMCode(c.curl_multi_remove_handle(self.multi, handle), &self.diagnostics); } /// Performs transfers on all the added handles that need attention in a non-blocking fashion. /// Returns the number of handles that still transfer data. When that reaches zero, all transfers are done. /// https://curl.se/libcurl/c/curl_multi_perform.html -pub fn perform(self: Self) !c_int { +pub fn perform(self: *Self) !c_int { var still_running: c_int = undefined; - try checkMCode(c.curl_multi_perform(self.multi, &still_running), self.diagnostics); + try checkMCode(c.curl_multi_perform(self.multi, &still_running), &self.diagnostics); return still_running; } @@ -62,7 +62,7 @@ pub fn perform(self: Self) !c_int { /// Polls all file descriptors used by the curl easy handles contained in the given multi handle set. /// Return the number of file descriptors on which there is activity. /// https://curl.se/libcurl/c/curl_multi_poll.html -pub fn poll(self: Self, extra_fds: ?[]c.curl_waitfd, timeout_ms: c_int) !c_int { +pub fn poll(self: *Self, extra_fds: ?[]c.curl_waitfd, timeout_ms: c_int) !c_int { var num_fds: c_int = undefined; var fds: ?[*]c.curl_waitfd = null; var fd_len: c_uint = 0; @@ -71,15 +71,15 @@ pub fn poll(self: Self, extra_fds: ?[]c.curl_waitfd, timeout_ms: c_int) !c_int { fd_len = @intCast(v.len); } - try checkMCode(c.curl_multi_poll(self.multi, fds, fd_len, timeout_ms, &num_fds), self.diagnostics); + try checkMCode(c.curl_multi_poll(self.multi, fds, fd_len, timeout_ms, &num_fds), &self.diagnostics); return num_fds; } /// Wakes up a sleeping curl_multi_poll call that is currently (or is about to be) waiting for activity or a timeout. /// This function can be called from any thread. /// https://curl.se/libcurl/c/curl_multi_wakeup.html -pub fn wakeup(self: Self) !void { - try checkMCode(c.curl_multi_wakeup(self.multi), self.diagnostics); +pub fn wakeup(self: *Self) !void { + try checkMCode(c.curl_multi_wakeup(self.multi), &self.diagnostics); } pub const Info = struct { @@ -89,7 +89,7 @@ pub const Info = struct { /// Ask the multi handle if there are any messages from the individual transfers. /// https://curl.se/libcurl/c/curl_multi_info_read.html -pub fn readInfo(self: Self) !Info { +pub fn readInfo(self: *Self) !Info { var msgs_in_queue: c_int = undefined; const msg = c.curl_multi_info_read(self.multi, &msgs_in_queue); diff --git a/src/errors.zig b/src/errors.zig index 53dbf09..8bd66cb 100644 --- a/src/errors.zig +++ b/src/errors.zig @@ -39,6 +39,15 @@ pub const Diagnostics = struct { /// https://curl.se/libcurl/c/libcurl-errors.html#CURLMcode m_code: c.CURLMcode, } = null, + + /// 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)), + }; + } }; pub fn checkCode(code: c.CURLcode, diagnostics: ?*Diagnostics) !void { @@ -48,9 +57,6 @@ pub fn checkCode(code: c.CURLcode, diagnostics: ?*Diagnostics) !void { if (diagnostics) |diag| diag.error_code = .{ .code = code }; - // https://curl.se/libcurl/c/libcurl-errors.html - std.log.debug("curl err code:{d}, msg:{s}\n", .{ code, c.curl_easy_strerror(code) }); - return error.Curl; } @@ -61,11 +67,5 @@ pub fn checkMCode(code: c.CURLMcode, diagnostics: ?*Diagnostics) !void { if (diagnostics) |diag| diag.error_code = .{ .m_code = code }; - // https://curl.se/libcurl/c/libcurl-errors.html - std.log.debug("curlm err code:{d}, msg:{s}\n", .{ - code, - c.curl_multi_strerror(code), - }); - return error.Curl; } From fe30a4859ca9ed1da723e1480c0977886fa96fff Mon Sep 17 00:00:00 2001 From: Jiacai Liu Date: Sun, 8 Mar 2026 12:02:16 +0800 Subject: [PATCH 2/2] Update examples/multi.zig Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- examples/multi.zig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/multi.zig b/examples/multi.zig index f587ddc..5c0b97d 100644 --- a/examples/multi.zig +++ b/examples/multi.zig @@ -27,7 +27,10 @@ pub fn main() !void { var multi = try Multi.init(); defer multi.deinit() catch |e| { - std.debug.print("multi handle deinit failed, err:{any}", .{e}); + std.debug.print("multi handle deinit failed, err:{any}\n", .{e}); + if (multi.diagnostics.getMessage()) |msg| { + std.debug.print("Diagnostics: {s}\n", .{msg}); + } }; var wtr1 = std.Io.Writer.Allocating.init(allocator);