filter: a failed blocklist download names its phase, cause, status, bytes and elapsed time
Gates / frontend (push) Successful in 2m20s
Gates / test (push) Failing after 2m41s
Gates / test-aarch64 (push) Successful in 8m19s
Gates / package (push) Successful in 4m24s
Gates / container (push) Successful in 16s
CI / gates (push) Failing after 41m33s

This commit is contained in:
2026-09-12 09:58:59 +02:00
parent bf79a22584
commit 52158198cf
7 changed files with 629 additions and 82 deletions
+46
View File
@@ -419,6 +419,52 @@ pub const Client = struct {
}
};
/// The unwrap helpers below turn the single collapsed error `std.http.Client`
/// reports into the concrete cause it stashed. Every HTTP caller in this tree
/// uses them: the DoH client classifies by the unwrapped cause, the blocklist
/// fetcher keeps its own classification and records the cause for the operator.
const Connection = std.http.Client.Connection;
const Request = std.http.Client.Request;
const Response = std.http.Client.Response;
/// `std.Io.Writer` collapses every send failure to `error.WriteFailed` and
/// stashes the cause on the connection's socket writer. Unwrapping it is what
/// keeps `error.Canceled` and the local resource errors out of the peer fault
/// group.
pub fn sendCause(req: *const Request, err: anyerror) anyerror {
if (err != error.WriteFailed) return err;
const connection = req.connection orelse return err;
return connection.stream_writer.err orelse err;
}
/// `receiveHead` collapses a transport failure to `error.ReadFailed` and names
/// `Connection.getReadError` as the accessor for the concrete cause.
pub fn headCause(req: *const Request, err: anyerror) anyerror {
if (err != error.ReadFailed) return err;
const connection = req.connection orelse return err;
return readCause(connection, err);
}
/// A body read reports two different kinds of failure through the same
/// `error.ReadFailed`. An HTTP framing fault lands on the response, and only a
/// read that never reached the framing leaves the connection's cause, so the
/// response is consulted first.
pub fn bodyCause(resp: *const Response, err: anyerror) anyerror {
if (err != error.ReadFailed) return err;
if (resp.bodyErr()) |cause| return cause;
const connection = resp.request.connection orelse return err;
return readCause(connection, err);
}
/// `Connection.getReadError` reads the socket reader's stashed cause with `.?`.
/// On a plain connection that is its only source, so calling it with nothing
/// stashed would panic rather than return null; the guard keeps this unwrap
/// total on the path a plain connection can reach without TLS.
pub fn readCause(connection: *const Connection, err: anyerror) anyerror {
if (connection.protocol == .plain and connection.stream_reader.err == null) return err;
return connection.getReadError() orelse err;
}
pub const ValidateError = error{ BadResponse, ResponseMismatch };
/// RFC 9619: exactly one question on both sides. RFC 4343: names compare