Skip to content

Commit 9e7839d

Browse files
jiacai2050Copilot
andauthored
feat(diagnostics): Improve error diagnostics usability (#47)
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. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent b220077 commit 9e7839d

5 files changed

Lines changed: 54 additions & 34 deletions

File tree

README.org

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#+TITLE: zig-curl
22
#+DATE: 2023-09-16T23:16:15+0800
3-
#+LASTMOD: 2025-10-17T09:10:36+0800
3+
#+LASTMOD: 2026-03-08T11:50:51+0800
44
#+OPTIONS: toc:nil num:nil
55
#+STARTUP: content
66

@@ -61,6 +61,23 @@ pub fn main() !void {
6161

6262
Check [[file:examples]] for more examples.
6363

64+
** Diagnostics
65+
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=.
66+
67+
#+begin_src zig
68+
var easy = try curl.Easy.init(.{ ... });
69+
defer easy.deinit();
70+
71+
easy.perform() catch |err| {
72+
if (err == error.Curl) {
73+
if (easy.diagnostics.getMessage()) |msg| {
74+
std.log.err("curl failed: {s}", .{msg});
75+
}
76+
}
77+
return err;
78+
};
79+
#+end_src
80+
6481
* Documentation
6582
See https://jiacai2050.github.io/zig-curl/
6683

examples/header.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ fn iterateRedirectedHeaders(easy: *Easy) !void {
3333
try easy.setFollowLocation(true);
3434
const resp = try easy.fetch("https://edgebin.liujiacai.net/redirect/2", .{});
3535

36-
var diagnostics: Easy.Diagnostics = .{};
36+
var diagnostics: curl.Diagnostics = .{};
3737
const redirects = try resp.getRedirectCount(&diagnostics);
3838
try std.testing.expectEqual(redirects, 2);
3939

examples/multi.zig

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,13 @@ pub fn main() !void {
2525
defer if (gpa.deinit() != .ok) @panic("leak");
2626
const allocator = gpa.allocator();
2727

28-
var diagnostics: Multi.Diagnostics = .{};
29-
30-
const multi = try Multi.init(&diagnostics);
31-
defer multi.deinit();
28+
var multi = try Multi.init();
29+
defer multi.deinit() catch |e| {
30+
std.debug.print("multi handle deinit failed, err:{any}\n", .{e});
31+
if (multi.diagnostics.getMessage()) |msg| {
32+
std.debug.print("Diagnostics: {s}\n", .{msg});
33+
}
34+
};
3235

3336
var wtr1 = std.Io.Writer.Allocating.init(allocator);
3437
defer wtr1.deinit();
@@ -67,16 +70,16 @@ pub fn main() !void {
6770
}
6871

6972
// check that the request was successful
70-
try checkCode(info.msg.data.result, &diagnostics);
73+
try checkCode(info.msg.data.result, &multi.diagnostics);
7174

7275
// Read the HTTP status code
7376
var status_code: c_long = 0;
74-
try checkCode(c.curl_easy_getinfo(easy_handle, c.CURLINFO_RESPONSE_CODE, &status_code), &diagnostics);
77+
try checkCode(c.curl_easy_getinfo(easy_handle, c.CURLINFO_RESPONSE_CODE, &status_code), &multi.diagnostics);
7578
std.debug.print("Response Code: {any}\n", .{status_code});
7679

7780
// Get the private data (buffer) associated with this handle
7881
var private_data: ?*anyopaque = null;
79-
try checkCode(c.curl_easy_getinfo(easy_handle, c.CURLINFO_PRIVATE, &private_data), &diagnostics);
82+
try checkCode(c.curl_easy_getinfo(easy_handle, c.CURLINFO_PRIVATE, &private_data), &multi.diagnostics);
8083
const writer: *Writer = @ptrCast(@alignCast(private_data.?));
8184

8285
std.debug.print("Response body: {s}\n", .{writer.buffered()});

src/Multi.zig

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,50 +19,50 @@ pub const Diagnostics = errors.Diagnostics;
1919
const Self = @This();
2020

2121
multi: *c.CURLM,
22-
diagnostics: ?*Diagnostics,
22+
diagnostics: Diagnostics,
2323

24-
pub fn init(diagnostics: ?*Diagnostics) !Self {
24+
pub fn init() !Self {
2525
const core = c.curl_multi_init();
2626
if (core == null) {
2727
return error.InitMulti;
2828
}
2929
return .{
3030
.multi = core.?,
31-
.diagnostics = diagnostics,
31+
.diagnostics = .{},
3232
};
3333
}
3434

35-
pub fn deinit(self: Self) void {
36-
_ = self;
35+
pub fn deinit(self: *Self) !void {
36+
return checkMCode(c.curl_multi_cleanup(self.multi), &self.diagnostics);
3737
}
3838

3939
/// Adds the easy handle to the multi_handle.
4040
/// https://curl.se/libcurl/c/curl_multi_add_handle.html
41-
pub fn addHandle(self: Self, easy: *Easy) !void {
41+
pub fn addHandle(self: *Self, easy: *Easy) !void {
4242
try easy.setCommonOpts();
43-
return checkMCode(c.curl_multi_add_handle(self.multi, easy.handle), self.diagnostics);
43+
return checkMCode(c.curl_multi_add_handle(self.multi, easy.handle), &self.diagnostics);
4444
}
4545

4646
/// Removes a given easy_handle from the multi_handle.
4747
/// https://curl.se/libcurl/c/curl_multi_remove_handle.html
48-
pub fn removeHandle(self: Self, handle: *c.CURL) !void {
49-
return checkMCode(c.curl_multi_remove_handle(self.multi, handle), self.diagnostics);
48+
pub fn removeHandle(self: *Self, handle: *c.CURL) !void {
49+
return checkMCode(c.curl_multi_remove_handle(self.multi, handle), &self.diagnostics);
5050
}
5151

5252
/// Performs transfers on all the added handles that need attention in a non-blocking fashion.
5353
/// Returns the number of handles that still transfer data. When that reaches zero, all transfers are done.
5454
/// https://curl.se/libcurl/c/curl_multi_perform.html
55-
pub fn perform(self: Self) !c_int {
55+
pub fn perform(self: *Self) !c_int {
5656
var still_running: c_int = undefined;
57-
try checkMCode(c.curl_multi_perform(self.multi, &still_running), self.diagnostics);
57+
try checkMCode(c.curl_multi_perform(self.multi, &still_running), &self.diagnostics);
5858

5959
return still_running;
6060
}
6161

6262
/// Polls all file descriptors used by the curl easy handles contained in the given multi handle set.
6363
/// Return the number of file descriptors on which there is activity.
6464
/// https://curl.se/libcurl/c/curl_multi_poll.html
65-
pub fn poll(self: Self, extra_fds: ?[]c.curl_waitfd, timeout_ms: c_int) !c_int {
65+
pub fn poll(self: *Self, extra_fds: ?[]c.curl_waitfd, timeout_ms: c_int) !c_int {
6666
var num_fds: c_int = undefined;
6767
var fds: ?[*]c.curl_waitfd = null;
6868
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 {
7171
fd_len = @intCast(v.len);
7272
}
7373

74-
try checkMCode(c.curl_multi_poll(self.multi, fds, fd_len, timeout_ms, &num_fds), self.diagnostics);
74+
try checkMCode(c.curl_multi_poll(self.multi, fds, fd_len, timeout_ms, &num_fds), &self.diagnostics);
7575
return num_fds;
7676
}
7777

7878
/// Wakes up a sleeping curl_multi_poll call that is currently (or is about to be) waiting for activity or a timeout.
7979
/// This function can be called from any thread.
8080
/// https://curl.se/libcurl/c/curl_multi_wakeup.html
81-
pub fn wakeup(self: Self) !void {
82-
try checkMCode(c.curl_multi_wakeup(self.multi), self.diagnostics);
81+
pub fn wakeup(self: *Self) !void {
82+
try checkMCode(c.curl_multi_wakeup(self.multi), &self.diagnostics);
8383
}
8484

8585
pub const Info = struct {
@@ -89,7 +89,7 @@ pub const Info = struct {
8989

9090
/// Ask the multi handle if there are any messages from the individual transfers.
9191
/// https://curl.se/libcurl/c/curl_multi_info_read.html
92-
pub fn readInfo(self: Self) !Info {
92+
pub fn readInfo(self: *Self) !Info {
9393
var msgs_in_queue: c_int = undefined;
9494

9595
const msg = c.curl_multi_info_read(self.multi, &msgs_in_queue);

src/errors.zig

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,15 @@ pub const Diagnostics = struct {
3939
/// https://curl.se/libcurl/c/libcurl-errors.html#CURLMcode
4040
m_code: c.CURLMcode,
4141
} = null,
42+
43+
/// Returns a human-readable error message based on the error code.
44+
pub fn getMessage(self: Diagnostics) ?[]const u8 {
45+
const error_code = self.error_code orelse return null;
46+
return switch (error_code) {
47+
.code => |code| std.mem.span(c.curl_easy_strerror(code)),
48+
.m_code => |m_code| std.mem.span(c.curl_multi_strerror(m_code)),
49+
};
50+
}
4251
};
4352

4453
pub fn checkCode(code: c.CURLcode, diagnostics: ?*Diagnostics) !void {
@@ -48,9 +57,6 @@ pub fn checkCode(code: c.CURLcode, diagnostics: ?*Diagnostics) !void {
4857

4958
if (diagnostics) |diag| diag.error_code = .{ .code = code };
5059

51-
// https://curl.se/libcurl/c/libcurl-errors.html
52-
std.log.debug("curl err code:{d}, msg:{s}\n", .{ code, c.curl_easy_strerror(code) });
53-
5460
return error.Curl;
5561
}
5662

@@ -61,11 +67,5 @@ pub fn checkMCode(code: c.CURLMcode, diagnostics: ?*Diagnostics) !void {
6167

6268
if (diagnostics) |diag| diag.error_code = .{ .m_code = code };
6369

64-
// https://curl.se/libcurl/c/libcurl-errors.html
65-
std.log.debug("curlm err code:{d}, msg:{s}\n", .{
66-
code,
67-
c.curl_multi_strerror(code),
68-
});
69-
7070
return error.Curl;
7171
}

0 commit comments

Comments
 (0)