better handling of internet issues on initial requests

also improve json output
This commit is contained in:
mikkelam 2025-06-19 19:37:21 +02:00
parent 39d2cbd9d7
commit 08643d487c
2 changed files with 64 additions and 23 deletions

View file

@ -13,16 +13,6 @@ const progress = @import("../lib/progress.zig");
const HttpLatencyTester = @import("../lib/http_latency_tester.zig").HttpLatencyTester; const HttpLatencyTester = @import("../lib/http_latency_tester.zig").HttpLatencyTester;
const log = std.log.scoped(.cli); const log = std.log.scoped(.cli);
/// Update spinner text with current speed measurement
fn updateSpinnerText(spinner: anytype, measurement: SpeedMeasurement) void {
spinner.updateText("⬇️ {d:.1} {s}", .{ measurement.value, measurement.unit.toString() }) catch {};
}
/// Update spinner text with current upload speed measurement
fn updateUploadSpinnerText(spinner: anytype, measurement: SpeedMeasurement) void {
spinner.updateText("⬆️ {d:.1} {s}", .{ measurement.value, measurement.unit.toString() }) catch {};
}
const https_flag = zli.Flag{ const https_flag = zli.Flag{
.name = "https", .name = "https",
.description = "Use https when connecting to fast.com", .description = "Use https when connecting to fast.com",
@ -86,7 +76,11 @@ fn run(ctx: zli.CommandContext) !void {
if (!json_output) { if (!json_output) {
try ctx.spinner.fail("Failed to get URLs: {}", .{err}); try ctx.spinner.fail("Failed to get URLs: {}", .{err});
} else { } else {
std.debug.print("{{\"error\": \"{}\"}}\n", .{err}); const error_msg = switch (err) {
error.ConnectionTimeout => "Failed to contact fast.com servers",
else => "Failed to get URLs",
};
try outputJson(null, null, null, error_msg);
} }
return; return;
}; };
@ -133,7 +127,7 @@ fn run(ctx: zli.CommandContext) !void {
// JSON mode: clean output only // JSON mode: clean output only
break :blk speed_tester.measure_download_speed_stability(urls, criteria) catch |err| { break :blk speed_tester.measure_download_speed_stability(urls, criteria) catch |err| {
log.err("Download test failed: {}", .{err}); log.err("Download test failed: {}", .{err});
std.debug.print("{{\"error\": \"{}\"}}\n", .{err}); try outputJson(null, null, null, "Download test failed");
return; return;
}; };
} else blk: { } else blk: {
@ -155,7 +149,7 @@ fn run(ctx: zli.CommandContext) !void {
// JSON mode: clean output only // JSON mode: clean output only
break :blk speed_tester.measure_upload_speed_stability(urls, criteria) catch |err| { break :blk speed_tester.measure_upload_speed_stability(urls, criteria) catch |err| {
log.err("Upload test failed: {}", .{err}); log.err("Upload test failed: {}", .{err});
std.debug.print("{{\"error\": \"{}\"}}\n", .{err}); try outputJson(download_result.speed.value, latency_ms, null, "Upload test failed");
return; return;
}; };
} else blk: { } else blk: {
@ -184,13 +178,33 @@ fn run(ctx: zli.CommandContext) !void {
} }
} }
} else { } else {
std.debug.print("{{\"download_mbps\": {d:.1}", .{download_result.speed.value}); const upload_speed = if (upload_result) |up| up.speed.value else null;
if (latency_ms) |ping| { try outputJson(download_result.speed.value, latency_ms, upload_speed, null);
std.debug.print(", \"ping_ms\": {d:.1}", .{ping});
}
if (upload_result) |up| {
std.debug.print(", \"upload_mbps\": {d:.1}", .{up.speed.value});
}
std.debug.print("}}\n", .{});
} }
} }
/// Update spinner text with current speed measurement
fn updateSpinnerText(spinner: anytype, measurement: SpeedMeasurement) void {
spinner.updateText("⬇️ {d:.1} {s}", .{ measurement.value, measurement.unit.toString() }) catch {};
}
/// Update spinner text with current upload speed measurement
fn updateUploadSpinnerText(spinner: anytype, measurement: SpeedMeasurement) void {
spinner.updateText("⬆️ {d:.1} {s}", .{ measurement.value, measurement.unit.toString() }) catch {};
}
fn outputJson(download_mbps: ?f64, ping_ms: ?f64, upload_mbps: ?f64, error_message: ?[]const u8) !void {
const stdout = std.io.getStdOut().writer();
var download_buf: [32]u8 = undefined;
var ping_buf: [32]u8 = undefined;
var upload_buf: [32]u8 = undefined;
var error_buf: [256]u8 = undefined;
const download_str = if (download_mbps) |d| try std.fmt.bufPrint(&download_buf, "{d:.1}", .{d}) else "null";
const ping_str = if (ping_ms) |p| try std.fmt.bufPrint(&ping_buf, "{d:.1}", .{p}) else "null";
const upload_str = if (upload_mbps) |u| try std.fmt.bufPrint(&upload_buf, "{d:.1}", .{u}) else "null";
const error_str = if (error_message) |e| try std.fmt.bufPrint(&error_buf, "\"{s}\"", .{e}) else "null";
try stdout.print("{{\"download_mbps\": {s}, \"ping_ms\": {s}, \"upload_mbps\": {s}, \"error\": {s}}}\n", .{ download_str, ping_str, upload_str, error_str });
}

View file

@ -6,11 +6,13 @@ const testing = std.testing;
const log = std.log.scoped(.fast_api); const log = std.log.scoped(.fast_api);
const mvzr = @import("mvzr"); const mvzr = @import("mvzr");
const FastError = error{ const FastError = error{
HttpRequestFailed, HttpRequestFailed,
ScriptNotFound, ScriptNotFound,
TokenNotFound, TokenNotFound,
JsonParseError, JsonParseError,
ConnectionTimeout,
}; };
const Location = struct { const Location = struct {
@ -159,11 +161,36 @@ pub const Fast = struct {
_ = allocator; _ = allocator;
var response_body = std.ArrayList(u8).init(self.arena.allocator()); var response_body = std.ArrayList(u8).init(self.arena.allocator());
const response: http.Client.FetchResult = try self.client.fetch(.{ const response: http.Client.FetchResult = self.client.fetch(.{
.method = .GET, .method = .GET,
.location = .{ .url = url }, .location = .{ .url = url },
.response_storage = .{ .dynamic = &response_body }, .response_storage = .{ .dynamic = &response_body },
}); }) catch |err| switch (err) {
error.NetworkUnreachable, error.ConnectionRefused => {
log.err("Failed to reach fast.com servers (network/connection error) for URL: {s}", .{url});
return error.ConnectionTimeout;
},
error.UnknownHostName, error.NameServerFailure, error.TemporaryNameServerFailure, error.HostLacksNetworkAddresses => {
log.err("Failed to resolve fast.com hostname (DNS/internet connection issue) for URL: {s}", .{url});
return error.ConnectionTimeout;
},
error.ConnectionTimedOut, error.ConnectionResetByPeer => {
log.err("Connection to fast.com servers timed out or was reset for URL: {s}", .{url});
return error.ConnectionTimeout;
},
error.TlsInitializationFailed => {
log.err("Failed to establish secure connection to fast.com servers for URL: {s}", .{url});
return error.ConnectionTimeout;
},
error.UnexpectedConnectFailure => {
log.err("Unexpected connection failure to fast.com servers for URL: {s}", .{url});
return error.ConnectionTimeout;
},
else => {
log.err("Network error: {} for URL: {s}", .{ err, url });
return error.ConnectionTimeout;
},
};
log.debug("HTTP response status: {} for URL: {s}", .{ response.status, url }); log.debug("HTTP response status: {} for URL: {s}", .{ response.status, url });