milestone 19: hygiene sweep - dead ecs surface, single-source constants, tls classification, frontend state hazards, docker smoke network fix
This commit is contained in:
+33
-6
@@ -156,6 +156,32 @@ fn parseUrl(url: []const u8) Error!std.Uri {
|
||||
/// first time two phases shared an error.
|
||||
const Phase = enum { connect, send, receive };
|
||||
|
||||
// `error.X` in an expression names a member into existence rather than
|
||||
// referring to one, so the switch in `mapError` would keep compiling — and
|
||||
// silently stop matching — if std renamed either of these. This is what fails
|
||||
// the build instead.
|
||||
comptime {
|
||||
for ([_][]const u8{ "TlsInitializationFailed", "CertificateBundleLoadFailure" }) |name| {
|
||||
if (!errorSetHas(std.http.Client.RequestError, name)) {
|
||||
@compileError("std.http.Client.RequestError no longer names " ++ name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn errorSetHas(comptime Set: type, comptime name: []const u8) bool {
|
||||
for (@typeInfo(Set).error_set.?) |member| {
|
||||
if (std.mem.eql(u8, member.name, name)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// The two names are the whole TLS surface this file can reach.
|
||||
/// `std.http.Client` collapses every handshake fault into
|
||||
/// `error.TlsInitializationFailed` (Client.zig:1470) and every bundle fault into
|
||||
/// `error.CertificateBundleLoadFailure`, and this file never unwraps a
|
||||
/// connection's stashed read cause — it maps the collapsed `error.ReadFailed` by
|
||||
/// phase — so the record-layer members of `std.crypto.tls.Client.ReadError`
|
||||
/// cannot arrive here. `doh_client.zig` does unwrap, and names them.
|
||||
fn mapError(err: anyerror, phase: Phase) Error {
|
||||
if (transport.mapLocal(err)) |local| return narrowLocal(local);
|
||||
switch (err) {
|
||||
@@ -165,9 +191,10 @@ fn mapError(err: anyerror, phase: Phase) Error {
|
||||
error.TooManyHttpRedirects => return error.HttpStatus,
|
||||
else => {},
|
||||
}
|
||||
const err_name = @errorName(err);
|
||||
if (std.mem.startsWith(u8, err_name, "Tls") or
|
||||
std.mem.startsWith(u8, err_name, "Certificate")) return error.TlsFailed;
|
||||
switch (err) {
|
||||
error.TlsInitializationFailed, error.CertificateBundleLoadFailure => return error.TlsFailed,
|
||||
else => {},
|
||||
}
|
||||
return switch (phase) {
|
||||
.connect => error.ConnectFailed,
|
||||
.send => error.SendFailed,
|
||||
@@ -307,10 +334,10 @@ test "mapError maps local errors before phase errors" {
|
||||
);
|
||||
}
|
||||
|
||||
test "mapError maps tls errors regardless of phase" {
|
||||
test "mapError maps the reachable tls errors regardless of phase" {
|
||||
try testing.expectEqual(error.TlsFailed, mapError(error.TlsInitializationFailed, .connect));
|
||||
try testing.expectEqual(error.TlsFailed, mapError(error.TlsAlert, .receive));
|
||||
try testing.expectEqual(error.TlsFailed, mapError(error.CertificateExpired, .connect));
|
||||
try testing.expectEqual(error.TlsFailed, mapError(error.TlsInitializationFailed, .receive));
|
||||
try testing.expectEqual(error.TlsFailed, mapError(error.CertificateBundleLoadFailure, .connect));
|
||||
}
|
||||
|
||||
test "mapError maps a redirect overrun to HttpStatus" {
|
||||
|
||||
@@ -1388,7 +1388,7 @@ fn requestHeader() header.Header {
|
||||
return (packet.parse(opt_query) catch unreachable).header;
|
||||
}
|
||||
|
||||
/// Answers `qname`/`qtype` from `table`, exactly as the Phase 7 handler will:
|
||||
/// Answers `qname`/`qtype` from `table`, exactly as `server/handler.zig` does:
|
||||
/// look the name up, then write the run it returns into a response.
|
||||
fn answerLocal(
|
||||
table: *const records.Records,
|
||||
|
||||
@@ -289,7 +289,7 @@ pub const Manager = struct {
|
||||
/// what every test and `nxdns check` want. Only the scheduler consults it —
|
||||
/// see `refreshGated`.
|
||||
monitor: ?*disk_monitor.Monitor = null,
|
||||
/// Scheduled refresh passes skipped by the disk gate. Phase 8's health
|
||||
/// Scheduled refresh passes skipped by the disk gate. The `/api/health`
|
||||
/// rollup reads it through `refreshesGated`.
|
||||
refreshes_gated: std.atomic.Value(u64) = .init(0),
|
||||
|
||||
@@ -610,8 +610,8 @@ pub const Manager = struct {
|
||||
///
|
||||
/// The status entry is found by row id, so a source added since the last
|
||||
/// `reload` has nowhere to record its outcome. `refreshAll` syncs the table
|
||||
/// before it refreshes anything, which is why it is the entry point Phase 8
|
||||
/// and the scheduler use.
|
||||
/// before it refreshes anything, which is why `POST /api/blocklists/update`
|
||||
/// and the scheduler both enter through `refreshAll`.
|
||||
pub fn refreshSource(self: *Manager, io: std.Io, row: sources_repo.SourceRow) Error!bool {
|
||||
self.refresh_lock.lockUncancelable(io);
|
||||
defer self.refresh_lock.unlock(io);
|
||||
@@ -1142,7 +1142,8 @@ pub const Manager = struct {
|
||||
/// a critically full disk must not take.
|
||||
///
|
||||
/// `reload` and `refreshAll` are deliberately not gated: both are operator
|
||||
/// actions (the composition root's startup load, Phase 8's manual refresh),
|
||||
/// actions (the composition root's startup load, `POST
|
||||
/// /api/blocklists/update`),
|
||||
/// and an operator who asks for a refresh on a full disk has asked for it.
|
||||
///
|
||||
/// Counting happens here, so a caller cannot skip a pass without recording
|
||||
|
||||
@@ -55,7 +55,7 @@ pub fn lookup(domain: []const u8) ?[]const u8 {
|
||||
|
||||
/// The rewritten question name for a matched query. Applying it — sending the
|
||||
/// rewritten question upstream and prefixing the answer with a CNAME from the
|
||||
/// original name to the target — is the handler's job (Phase 7). Nothing here
|
||||
/// original name to the target — is `server/handler.zig`'s job. Nothing here
|
||||
/// builds a response.
|
||||
pub fn rewrite(domain: []const u8) ?name.Name {
|
||||
const target = lookup(domain) orelse return null;
|
||||
|
||||
Reference in New Issue
Block a user