# std.Uri Reference (Zig 0.16.0) Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html URI parsing remains mostly independent of the I/O migration. For network or file operations derived from URIs, pass/use `std.Io`. URI parsing and formatting that roughly adhere to RFC 3986, with percent-encoding/decoding and resolution support. The parser intentionally does not perform complete grammar or character-class validation. ## Table of Contents - [Parsing URIs](#parsing-uris) - [URI Components](#uri-components) - [Formatting URIs](#formatting-uris) - [Percent Encoding/Decoding](#percent-encodingdecoding) - [URI Resolution](#uri-resolution) - [Common Patterns](#common-patterns) ## Parsing URIs ### Basic Parsing ```zig const std = @import("std"); pub fn main() !void { const uri = try std.Uri.parse("https://user:pass@example.com:8080/path?query=1#fragment"); std.debug.print("Scheme: {s}\n", .{uri.scheme}); // "https" std.debug.print("User: {s}\n", .{uri.user.?.percent_encoded}); // "user" std.debug.print("Password: {s}\n", .{uri.password.?.percent_encoded}); // "pass" std.debug.print("Host: {s}\n", .{uri.host.?.percent_encoded}); // "example.com" std.debug.print("Port: {d}\n", .{uri.port.?}); // 8080 std.debug.print("Path: {s}\n", .{uri.path.percent_encoded}); // "/path" std.debug.print("Query: {s}\n", .{uri.query.?.percent_encoded}); // "query=1" std.debug.print("Fragment: {s}\n", .{uri.fragment.?.percent_encoded}); // "fragment" } ``` ### Parse After Scheme For URIs where scheme is already known (e.g., HTTP redirects): ```zig // Parse "//example.com/path" as an HTTP URI const uri = try std.Uri.parseAfterScheme("http", "//example.com/path"); std.debug.print("Scheme: {s}, Host: {s}\n", .{ uri.scheme, uri.host.?.percent_encoded, }); ``` ### Error Handling ```zig const uri = std.Uri.parse(input) catch |err| switch (err) { error.UnexpectedCharacter => { std.debug.print("Invalid character in URI\n", .{}); return err; }, error.InvalidFormat => { std.debug.print("Malformed URI\n", .{}); return err; }, error.InvalidPort => { std.debug.print("Port not a valid u16\n", .{}); return err; }, error.InvalidHostName => { std.debug.print("Invalid host name\n", .{}); return err; }, }; ``` ## URI Components ### Uri Struct ```zig const Uri = struct { scheme: []const u8, user: ?Component = null, password: ?Component = null, host: ?Component = null, port: ?u16 = null, path: Component = Component.empty, query: ?Component = null, fragment: ?Component = null, }; ``` ### Component Union Components can be raw (needs encoding) or already percent-encoded: ```zig const Component = union(enum) { /// Needs percent encoding before use in URI raw: []const u8, /// Already percent-encoded, can be used directly percent_encoded: []const u8, pub const empty: Component = .{ .percent_encoded = "" }; pub fn isEmpty(component: Component) bool; }; ``` ### Getting Host ```zig var buffer: [std.Io.net.HostName.max_len]u8 = undefined; const host = uri.getHost(&buffer) catch |err| switch (err) { error.UriMissingHost => return error.NoHost, }; ``` The result is a `std.Io.net.HostName`. A URI host is validated during parsing, so the fixed buffer is sized to the hostname limit and `getHost` exposes only `error.UriMissingHost`. With allocation: ```zig var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); const host = try uri.getHostAlloc(arena.allocator()); ``` ### Creating URIs Programmatically ```zig const uri: std.Uri = .{ .scheme = "https", .host = .{ .raw = "example.com" }, .port = 8080, .path = .{ .raw = "/api/users" }, .query = .{ .raw = "page=1&limit=10" }, }; ``` ### Component Methods ```zig const component: std.Uri.Component = .{ .percent_encoded = "hello%20world" }; // Check if empty if (component.isEmpty()) { // ... } // Get raw (decoded) value with buffer var buf: [256]u8 = undefined; const raw = try component.toRaw(&buf); // "hello world" // Get raw (decoded) value with allocation (only allocates if needed) const raw_alloc = try component.toRawMaybeAlloc(allocator); // "hello world" ``` `toRawMaybeAlloc` may return the component's original borrowed slice or arena-allocated decoded bytes. Treat the result as tied to both lifetimes and do not free it individually. ## Formatting URIs ### Full URI ```zig const uri = try std.Uri.parse("https://example.com:8080/path?query#frag"); var buf: [1024]u8 = undefined; var writer: std.Io.Writer = .fixed(&buf); try uri.format(&writer); const formatted = writer.buffered(); // "https://example.com:8080/path?query#frag" ``` ### Selective Formatting ```zig var buf: [1024]u8 = undefined; var writer: std.Io.Writer = .fixed(&buf); // Format only specific parts try std.fmt.format(&writer, "{f}", .{uri.fmt(.{ .scheme = true, .authority = true, .path = true, .query = true, .fragment = false, // omit fragment })}); ``` ### Format Flags ```zig const Flags = struct { scheme: bool = false, // Include scheme (e.g., "https:") authentication: bool = false, // Include user:password@ (requires authority) authority: bool = false, // Include host and port path: bool = false, // Include path query: bool = false, // Include ?query (requires path) fragment: bool = false, // Include #fragment (requires path) port: bool = true, // Include :port (requires authority) pub const all: Flags = .{ .scheme = true, .authentication = true, .authority = true, .path = true, .query = true, .fragment = true, .port = true, }; }; ``` ### Component Formatting Methods ```zig var buf: [256]u8 = undefined; var writer: std.Io.Writer = .fixed(&buf); const component: std.Uri.Component = .{ .raw = "hello world" }; // Different encoding rules for different URI parts try component.formatEscaped(&writer); // General: unreserved chars only try component.formatUser(&writer); // User: unreserved + sub-delims try component.formatPassword(&writer); // Password: user chars + ':' try component.formatHost(&writer); // Host: password chars + '[' + ']' try component.formatPath(&writer); // Path: user chars + '/' + ':' + '@' try component.formatQuery(&writer); // Query: path chars + '?' try component.formatFragment(&writer); // Fragment: same as query // Get raw (decoded) output try component.formatRaw(&writer); // Decodes percent-encoded chars ``` ## Percent Encoding/Decoding ### Decode In Place ```zig var buffer = "hello%20world%21".*; const decoded = std.Uri.percentDecodeInPlace(&buffer); // decoded == "hello world!" ``` ### Decode Backwards (Conditionally Safe for Aliasing) ```zig const input = "%48%65%6C%6C%6F"; var output: [5]u8 = undefined; const decoded = std.Uri.percentDecodeBackwards(&output, input); // decoded == "Hello" ``` The output must be large enough. Aliasing is supported only when `output.ptr <= input.ptr`; there is no recoverable undersized-buffer error. ### Encode with Component ```zig var buf: [256]u8 = undefined; var writer: std.Io.Writer = .fixed(&buf); // Raw component will be percent-encoded when formatted const component: std.Uri.Component = .{ .raw = "hello world!" }; try component.formatPath(&writer); const encoded = writer.buffered(); // "hello%20world%21" ``` ### Custom Percent Encoding ```zig var buf: [256]u8 = undefined; var writer: std.Io.Writer = .fixed(&buf); // Encode with custom character validation try std.Uri.Component.percentEncode(&writer, "custom data", struct { fn isValid(c: u8) bool { return std.ascii.isAlphanumeric(c); } }.isValid); ``` ## URI Resolution ### Resolve Relative URI Resolves a relative URI against a base URI per RFC 3986 Section 5: ```zig const base = try std.Uri.parse("http://a/b/c/d;p?q"); var aux_buf: [1024]u8 = undefined; var aux_slice: []u8 = &aux_buf; // Copy relative URI to start of aux_buf const relative = "../g"; @memcpy(aux_buf[0..relative.len], relative); const resolved = try std.Uri.resolveInPlace(base, relative.len, &aux_slice); // resolved.path.percent_encoded == "/a/g" ``` ### Resolution Examples (RFC 3986) | Base: `http://a/b/c/d;p?q` | Reference | Result | |---------------------------|-----------|--------| | | `g` | `http://a/b/c/g` | | | `./g` | `http://a/b/c/g` | | | `g/` | `http://a/b/c/g/` | | | `/g` | `http://a/g` | | | `//g` | `http://g` | | | `?y` | `http://a/b/c/d;p?y` | | | `g?y` | `http://a/b/c/g?y` | | | `#s` | `http://a/b/c/d;p?q#s` | | | `g#s` | `http://a/b/c/g#s` | | | `../` | `http://a/b/` | | | `../g` | `http://a/b/g` | | | `../../g` | `http://a/g` | ## Common Patterns ### Extract Query Parameters ```zig fn getQueryParam(uri: std.Uri, key: []const u8) ?[]const u8 { const query = uri.query orelse return null; const query_str = query.percent_encoded; var iter = std.mem.splitScalar(u8, query_str, '&'); while (iter.next()) |pair| { if (std.mem.indexOfScalar(u8, pair, '=')) |eq_pos| { if (std.mem.eql(u8, pair[0..eq_pos], key)) { return pair[eq_pos + 1 ..]; } } else if (std.mem.eql(u8, pair, key)) { return ""; // Key exists with no value } } return null; } // Usage const uri = try std.Uri.parse("https://example.com?name=alice&age=30"); const name = getQueryParam(uri, "name"); // "alice" ``` ### Build URL with Query Parameters ```zig fn buildUrl(allocator: std.mem.Allocator, base: []const u8, params: []const [2][]const u8) ![]u8 { var result: std.Io.Writer.Allocating = .init(allocator); errdefer result.deinit(); try result.writer.writeAll(base); for (params, 0..) |param, i| { try result.writer.writeByte(if (i == 0) '?' else '&'); try (std.Uri.Component{ .raw = param[0] }).formatEscaped(&result.writer); try result.writer.writeByte('='); try (std.Uri.Component{ .raw = param[1] }).formatEscaped(&result.writer); } return result.toOwnedSlice(); } ``` ### Serialize a Parsed URI ```zig fn serializeUri(allocator: std.mem.Allocator, uri_str: []const u8) ![]u8 { const uri = try std.Uri.parse(uri_str); var buf: [4096]u8 = undefined; var writer: std.Io.Writer = .fixed(&buf); // Parsed percent-encoded components are preserved when formatted. try uri.format(&writer); return try allocator.dupe(u8, writer.buffered()); } ``` This is serialization, not general normalization: existing escape spelling is preserved. Formatting with all components also emits `/` for an empty included path. ### Validate URI ```zig fn isAcceptedByUriParser(str: []const u8) bool { _ = std.Uri.parse(str) catch return false; return true; } fn hasHttpSchemeAndHost(str: []const u8) bool { const uri = std.Uri.parse(str) catch return false; return uri.host != null and (std.mem.eql(u8, uri.scheme, "http") or std.mem.eql(u8, uri.scheme, "https")); } ``` These remain lightweight parser/scheme checks, not complete URI or HTTP-URL validation. ### Join Path Segments ```zig const OwnedUri = struct { value: std.Uri, path_storage: []u8, fn deinit(self: *OwnedUri, allocator: std.mem.Allocator) void { allocator.free(self.path_storage); self.* = undefined; } }; fn joinPath(allocator: std.mem.Allocator, base_uri: std.Uri, segments: []const []const u8) !OwnedUri { var path: std.Io.Writer.Allocating = .init(allocator); errdefer path.deinit(); // Preserve/encode the existing component according to path rules. try base_uri.path.formatPath(&path.writer); // Each segment is escaped as a segment, so '/' inside a segment becomes %2F. for (segments) |seg| { const current = path.written(); if (current.len == 0 or current[current.len - 1] != '/') try path.writer.writeByte('/'); try (std.Uri.Component{ .raw = seg }).formatEscaped(&path.writer); } const owned_path = try path.toOwnedSlice(); var result = base_uri; result.path = .{ .percent_encoded = owned_path }; result.query = null; result.fragment = null; return .{ .value = result, .path_storage = owned_path }; } ``` The returned URI owns its assembled path; call `OwnedUri.deinit` after use. ### Extract Base URL ```zig fn getBaseUrl(allocator: std.mem.Allocator, uri: std.Uri) ![]u8 { var buf: [1024]u8 = undefined; var writer: std.Io.Writer = .fixed(&buf); try std.fmt.format(&writer, "{f}", .{uri.fmt(.{ .scheme = true, .authority = true, .port = true, })}); return try allocator.dupe(u8, writer.buffered()); } // Usage const uri = try std.Uri.parse("https://example.com:8080/path?query#frag"); const base = try getBaseUrl(allocator, uri); // "https://example.com:8080" ``` ## Error Types ### ParseError ```zig pub const ParseError = error{ UnexpectedCharacter, // Invalid character in URI component InvalidFormat, // Malformed URI structure InvalidPort, // Port not a valid u16 InvalidHostName, // Host did not pass std.Io.net.HostName validation }; ``` ### ResolveInPlaceError ```zig pub const ResolveInPlaceError = ParseError || error{ NoSpaceLeft, // Auxiliary buffer too small }; ``` ### Component Errors ```zig // getHost errors error.UriMissingHost // URI has no host component // toRaw errors error.NoSpaceLeft // Buffer too small for decoded string ```