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..5c0b97d 100644 --- a/examples/multi.zig +++ b/examples/multi.zig @@ -25,10 +25,13 @@ 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}\n", .{e}); + if (multi.diagnostics.getMessage()) |msg| { + std.debug.print("Diagnostics: {s}\n", .{msg}); + } + }; var wtr1 = std.Io.Writer.Allocating.init(allocator); defer wtr1.deinit(); @@ -67,16 +70,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; }