diff --git a/src/internal/xrpc/xrpc.zig b/src/internal/xrpc/xrpc.zig index dd2e853..f50095c 100644 --- a/src/internal/xrpc/xrpc.zig +++ b/src/internal/xrpc/xrpc.zig @@ -38,6 +38,21 @@ pub const XrpcClient = struct { }; } + /// Same as `init`, naming the calling application in the User-Agent rather + /// than zat. Anything issuing XRPC at scale should use this: the operator + /// on the other end wants to know which service is calling, not which + /// library it happens to be built on. + pub fn initWithUserAgent( + io: std.Io, + allocator: std.mem.Allocator, + host: []const u8, + user_agent: []const u8, + ) XrpcClient { + var client = init(io, allocator, host); + if (user_agent.len > 0) client.transport.user_agent = user_agent; + return client; + } + pub fn deinit(self: *XrpcClient) void { self.transport.deinit(); } @@ -389,6 +404,18 @@ fn isRetryableTransportErrorForMethod(method: std.http.Method, err: anyerror) bo // === tests === +test "client user agent override reaches the transport" { + const testing = std.testing; + + var named = XrpcClient.initWithUserAgent(std.Options.debug_io, testing.allocator, "https://bsky.social", "stream/1.2.3 (+https://example.invalid)"); + defer named.deinit(); + try testing.expectEqualStrings("stream/1.2.3 (+https://example.invalid)", named.transport.user_agent); + + var empty = XrpcClient.initWithUserAgent(std.Options.debug_io, testing.allocator, "https://bsky.social", ""); + defer empty.deinit(); + try testing.expectEqualStrings(@import("transport.zig").default_user_agent, empty.transport.user_agent); +} + test "build url without params" { var client = XrpcClient.init(std.Options.debug_io, std.testing.allocator, "https://bsky.social"); defer client.deinit();