std.Uri — Parsing e Manipulação de URIs
O tipo std.Uri implementa o parsing de URIs conforme a RFC 3986. Ele decompõe uma string de URI em seus componentes — scheme, host, porta, path, query e fragment — sem realizar nenhuma alocação de memória, retornando slices que apontam para a string original.
Visão Geral
const std = @import("std");
const Uri = std.Uri;
Estrutura do Tipo
pub const Uri = struct {
scheme: []const u8,
user: ?Component = null,
password: ?Component = null,
host: ?Component = null,
port: ?u16 = null,
path: Component,
query: ?Component = null,
fragment: ?Component = null,
pub const Component = union(enum) {
raw: []const u8,
percent_encoded: []const u8,
};
};
Anatomia de uma URI
https://usuario:senha@exemplo.com:8080/caminho/recurso?q=busca#secao
\___/ \_____/ \___/ \_________/ \__/ \______________/ \_____/ \____/
scheme user pass host port path query fragment
Funções Principais
// Parse de uma string URI
pub fn parse(uri_str: []const u8) !Uri
// Resolve uma referência relativa contra uma base
pub fn resolve(base: Uri, reference: Uri) Uri
// Formata a URI como string
pub fn format(self: Uri, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void
// Decodifica percent-encoding
pub fn percentDecode(allocator: Allocator, input: []const u8) ![]u8
// Codifica para percent-encoding
pub fn percentEncode(input: []const u8) PercentEncoded
Exemplo 1: Parsing de URLs
const std = @import("std");
pub fn main() !void {
const stdout = std.io.getStdOut().writer();
const urls = [_][]const u8{
"https://api.exemplo.com:443/v2/usuarios?page=1&limit=10#resultados",
"http://localhost:8080/health",
"postgres://admin:s3nh4@db.interno:5432/meuapp",
"file:///home/usuario/documento.txt",
};
for (urls) |url_str| {
const uri = std.Uri.parse(url_str) catch |err| {
try stdout.print("Erro ao parsear '{s}': {}\n", .{ url_str, err });
continue;
};
try stdout.print("URI: {s}\n", .{url_str});
try stdout.print(" Scheme: {s}\n", .{uri.scheme});
if (uri.host) |host| {
switch (host) {
.raw => |raw| try stdout.print(" Host: {s}\n", .{raw}),
.percent_encoded => |enc| try stdout.print(" Host: {s} (encoded)\n", .{enc}),
}
}
if (uri.port) |porta| {
try stdout.print(" Porta: {d}\n", .{porta});
}
switch (uri.path) {
.raw => |raw| try stdout.print(" Path: {s}\n", .{raw}),
.percent_encoded => |enc| try stdout.print(" Path: {s}\n", .{enc}),
}
if (uri.query) |query| {
switch (query) {
.raw => |raw| try stdout.print(" Query: {s}\n", .{raw}),
.percent_encoded => |enc| try stdout.print(" Query: {s}\n", .{enc}),
}
}
if (uri.fragment) |frag| {
switch (frag) {
.raw => |raw| try stdout.print(" Frag: {s}\n", .{raw}),
.percent_encoded => |enc| try stdout.print(" Frag: {s}\n", .{enc}),
}
}
try stdout.writeAll("\n");
}
}
Exemplo 2: Construtor de URLs
const std = @import("std");
fn construirUrl(
allocator: std.mem.Allocator,
base: []const u8,
caminho: []const u8,
params: []const [2][]const u8,
) ![]u8 {
var buf = std.ArrayList(u8).init(allocator);
defer buf.deinit();
const writer = buf.writer();
try writer.writeAll(base);
// Garante barra entre base e caminho
if (base.len > 0 and base[base.len - 1] != '/') {
if (caminho.len == 0 or caminho[0] != '/') {
try writer.writeByte('/');
}
}
try writer.writeAll(caminho);
// Query parameters
if (params.len > 0) {
try writer.writeByte('?');
for (params, 0..) |par, i| {
if (i > 0) try writer.writeByte('&');
try writer.writeAll(par[0]);
try writer.writeByte('=');
try writer.writeAll(par[1]);
}
}
return try buf.toOwnedSlice();
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const stdout = std.io.getStdOut().writer();
const params = [_][2][]const u8{
.{ "page", "1" },
.{ "limit", "20" },
.{ "sort", "nome" },
};
const url = try construirUrl(
allocator,
"https://api.exemplo.com",
"v2/usuarios",
¶ms,
);
defer allocator.free(url);
try stdout.print("URL construída: {s}\n", .{url});
// Verifica parseando
const uri = try std.Uri.parse(url);
try stdout.print("Scheme: {s}\n", .{uri.scheme});
if (uri.query) |q| {
switch (q) {
.raw => |raw| try stdout.print("Query: {s}\n", .{raw}),
.percent_encoded => |enc| try stdout.print("Query: {s}\n", .{enc}),
}
}
}
Exemplo 3: Extraindo Parâmetros de Query
const std = @import("std");
fn parseQueryParams(
query: []const u8,
allocator: std.mem.Allocator,
) !std.StringHashMap([]const u8) {
var params = std.StringHashMap([]const u8).init(allocator);
errdefer params.deinit();
var pares = std.mem.splitScalar(u8, query, '&');
while (pares.next()) |par| {
if (std.mem.indexOf(u8, par, "=")) |sep| {
const chave = par[0..sep];
const valor = par[sep + 1 ..];
try params.put(chave, valor);
}
}
return params;
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const url = "https://loja.com/busca?q=teclado+mecanico&categoria=perifericos&preco_max=500&ordenar=relevancia";
const uri = try std.Uri.parse(url);
const stdout = std.io.getStdOut().writer();
if (uri.query) |query_comp| {
const query_str = switch (query_comp) {
.raw => |raw| raw,
.percent_encoded => |enc| enc,
};
var params = try parseQueryParams(query_str, allocator);
defer params.deinit();
try stdout.writeAll("Parâmetros de busca:\n");
var it = params.iterator();
while (it.next()) |entry| {
try stdout.print(" {s} = {s}\n", .{
entry.key_ptr.*,
entry.value_ptr.*,
});
}
// Acesso direto
const busca = params.get("q") orelse "(vazio)";
try stdout.print("\nBusca: {s}\n", .{busca});
}
}
Módulos Relacionados
- std.http — Cliente e servidor HTTP
- std.net — Networking de baixo nível
- std.base64 — Codificação Base64
- std.mem — Operações com strings