//! The data the API returns, as types. //! //! **Field names are the API's own, in Portuguese** — `logradouro`, `bairro`, //! `localidade`. Everything else in this library is English; the fields are the //! one deliberate exception. //! //! The reason is fidelity: what you read in the JSON at //! is exactly what you access on the value. Translating to `street`/`city` //! would mean maintaining a field-by-field mapping and forcing anyone reading //! the API docs alongside this library to translate back in their head. The //! values are Portuguese either way — they come from the Correios database. //! //! ## Ownership //! //! `Address` and `Health` own their strings. Every one of their `[]const u8` //! fields points into memory the value allocated, and `deinit` releases it. //! Pass the same allocator the `Client` was built with, and treat every slice //! as invalid afterwards. //! //! `Faixa` is the exception: it allocates nothing and owns nothing. It travels //! inside a `Diagnostics`, which is a stack value, and borrows its storage. const std = @import("std"); const Allocator = std.mem.Allocator; /// The CEP's coordinate, when the database has one. /// /// The point is the **median** of the coordinates of that CEP's addresses, not /// the mean: one badly geocoded address 40 km away shifts a mean and does not /// move a median. /// /// Always check `precisao` before using `lat`/`lon` for distance: /// /// - `"logradouro"`: the point is a stretch of street. `raio_m` lands around /// 100–150 m. Good for maps and for distance. /// - `"localidade"`: the CEP *is* the whole municipality (a city-wide general /// CEP), and the point is its centroid. `raio_m` runs from 7 km to 40 km. /// Computing shipping from this is wrong by tens of kilometres. /// /// `raio_m` is the radius containing 90% of that CEP's addresses. It exists /// precisely so that error cannot pass unnoticed. pub const Geo = struct { /// Latitude in decimal degrees, WGS 84. lat: f64, /// Longitude in decimal degrees, WGS 84. lon: f64, /// Radius, in metres, containing 90% of the CEP's addresses. raio_m: i64, /// `"logradouro"` (a street) or `"localidade"` (a municipality centroid). /// Borrowed from the owning `Address`; invalid once that is deinitialised. precisao: []const u8, /// True when the coordinate points at a street, not a municipality. /// /// Shorthand for `precisao == "logradouro"`, which is the check to make /// before using the point to measure distance. pub fn isPrecise(g: Geo) bool { return std.mem.eql(u8, g.precisao, "logradouro"); } }; /// The string fields of an `Address`, in the API's own order. /// /// Used to keep the field list, the JSON keys and the CSV columns in step: all /// three are driven from this one declaration. pub const address_fields = [_][]const u8{ "cep", "logradouro", "complemento", "unidade", "bairro", "localidade", "uf", "estado", "regiao", "ibge", "gia", "ddd", "siafi", }; /// A CEP and everything the database knows about it. /// /// Fields the database does not have for that CEP come back as empty strings, /// never absent — that is what the API returns, and changing it here would /// create a difference between reading the JSON and reading this library. /// /// `geo` is the exception: it is null when there is no coordinate. There the /// difference is real, because the API **omits** the whole field in that case /// (about 43% of CEPs) rather than sending null. /// /// ## Ownership /// /// The value owns every byte its slices point at. Call `deinit` with the /// allocator the `Client` was built with exactly once; all slices, including /// `geo.precisao` and `raw`, are invalid from then on. pub const Address = struct { /// The CEP, always unmasked: `"01001000"`. cep: []const u8 = "", /// Street, avenue, square — the public way. logradouro: []const u8 = "", /// Range or side of the street the CEP covers, when it covers only part. complemento: []const u8 = "", /// Name of the large user or building the CEP belongs to, when it does. unidade: []const u8 = "", /// Neighbourhood. bairro: []const u8 = "", /// Municipality. localidade: []const u8 = "", /// Federative unit, two letters: `"SP"`. uf: []const u8 = "", /// Federative unit spelled out: `"São Paulo"`. estado: []const u8 = "", /// Region: `"Sudeste"`, `"Nordeste"`, and so on. regiao: []const u8 = "", /// IBGE municipality code — the key to join with public datasets. ibge: []const u8 = "", /// GIA code, São Paulo state tax administration only. gia: []const u8 = "", /// Area code for telephony: `"11"`. ddd: []const u8 = "", /// SIAFI municipality code, federal administration. siafi: []const u8 = "", /// The coordinate, or null when the database has none for this CEP. /// /// Always null for an address that came from `Client.lookupMany` — batches /// carry no coordinate at all. geo: ?Geo = null, /// The response body exactly as it arrived, uninterpreted, so a field the /// API grows before this library is updated is not lost. Parse it yourself /// with `std.json` when you need one. /// /// Empty for an address that came from `Client.lookupMany`: the batch /// endpoint answers in CSV, and a single row is not a document. raw: []const u8 = "", /// Backing storage for every string field above. Internal — do not read it /// and do not free it yourself; `deinit` does. bytes: []u8 = &.{}, /// Release everything the address owns. Pass the allocator the `Client` /// was built with. Every slice on the value is invalid afterwards. pub fn deinit(a: *Address, gpa: Allocator) void { gpa.free(a.bytes); gpa.free(a.raw); a.* = undefined; } /// Write the CEP with its mask — `01001-000` — into `buffer`. /// /// The native contract always returns it unmasked; formatting is /// presentation, and it lives here so it does not become a repeated /// `bufPrint` in every project that uses this. A CEP that is not 8 digits /// long is copied through unchanged. The result borrows `buffer`, which /// must hold at least 9 bytes. pub fn formattedCep(a: Address, buffer: []u8) []const u8 { if (a.cep.len != 8 or buffer.len < 9) { const n = @min(a.cep.len, buffer.len); @memcpy(buffer[0..n], a.cep[0..n]); return buffer[0..n]; } @memcpy(buffer[0..5], a.cep[0..5]); buffer[5] = '-'; @memcpy(buffer[6..9], a.cep[5..8]); return buffer[0..9]; } /// Build from the JSON object the API returned. /// /// Unknown fields are ignored during construction but preserved in `raw`. /// A new field in the API therefore cannot break this library. /// /// A malformed `geo` block yields `geo = null` rather than failing the /// whole value: it is not worth losing the address over, since the rest of /// the response is still useful. /// /// `body` is duplicated into `raw`; the caller keeps ownership of what it /// passed in. The returned `Address` owns its memory — call `deinit`. pub fn fromJson(gpa: Allocator, value: std.json.Value, body: []const u8) Allocator.Error!Address { var scratch: [address_fields.len][32]u8 = undefined; var values: [address_fields.len][]const u8 = undefined; inline for (address_fields, 0..) |name, i| { values[i] = jsonText(value, name, &scratch[i]); } const geo_source = geoFromJson(value); return build(gpa, &values, geo_source, body); } /// Build from one CSV record of the batch endpoint, given the header row. /// /// Columns the header does not mention are ignored; columns the header /// mentions but the record lacks come out empty. There is no `geo` column /// in the batch, so `geo` is always null and `raw` always empty. /// /// The returned `Address` owns its memory — call `deinit`. pub fn fromCsvRecord( gpa: Allocator, header: []const []const u8, record: []const []const u8, ) Allocator.Error!Address { var values: [address_fields.len][]const u8 = @splat(""); for (header, 0..) |column, i| { if (i >= record.len) break; inline for (address_fields, 0..) |name, slot| { if (std.mem.eql(u8, column, name)) values[slot] = record[i]; } } return build(gpa, &values, null, ""); } /// A `Geo` whose `precisao` still points at borrowed memory, as read out of /// a JSON block but before the strings are packed. Internal. const GeoSource = struct { lat: f64, lon: f64, raio_m: i64, precisao: []const u8 }; fn build( gpa: Allocator, values: *const [address_fields.len][]const u8, geo_source: ?GeoSource, body: []const u8, ) Allocator.Error!Address { var total: usize = 0; for (values) |v| total += v.len; if (geo_source) |g| total += g.precisao.len; const bytes = try gpa.alloc(u8, total); errdefer gpa.free(bytes); const raw = try gpa.dupe(u8, body); errdefer gpa.free(raw); var address: Address = .{ .bytes = bytes, .raw = raw }; var cursor: usize = 0; inline for (address_fields, 0..) |name, i| { const v = values[i]; @memcpy(bytes[cursor..][0..v.len], v); @field(address, name) = bytes[cursor..][0..v.len]; cursor += v.len; } if (geo_source) |g| { @memcpy(bytes[cursor..][0..g.precisao.len], g.precisao); address.geo = .{ .lat = g.lat, .lon = g.lon, .raio_m = g.raio_m, .precisao = bytes[cursor..][0..g.precisao.len], }; cursor += g.precisao.len; } return address; } fn geoFromJson(value: std.json.Value) ?GeoSource { const object = switch (value) { .object => |o| o, else => return null, }; const block = switch (object.get("geo") orelse return null) { .object => |o| o, else => return null, }; const precisao = switch (block.get("precisao") orelse return null) { .string, .number_string => |s| s, else => return null, }; return .{ .lat = jsonFloat(block.get("lat")) orelse return null, .lon = jsonFloat(block.get("lon")) orelse return null, .raio_m = jsonInt(block.get("raio_m")) orelse return null, .precisao = precisao, }; } }; /// The municipality a CEP that does **not** exist would have belonged to. /// /// `GET /{cep}` still answers **404** for a CEP the DNE does not have. When the /// number falls inside a band a municipality owns, that 404 now carries this /// block as well. It is **context for an error, never an address**: the CEP /// asked for does not exist, and nothing here says otherwise. Treating a /// non-null `faixa` as a successful lookup turns a clean failure into wrong /// data. /// /// It is worth reporting because a failing CEP is rarely random — it is usually /// someone who knows the city and guessed the number, and the classic guess is /// the `-000` that 957 municipalities never received. /// /// ## `cep_ini` and `cep_fim` are not the Correios' bounds /// /// They are the smallest and the largest CEP that **actually exist** in that /// municipality, not the range the Correios publish. For Areal (RJ) the DNE's /// own start is `25845000` — precisely the CEP that just failed — while /// `cep_ini` is `25845001`. /// /// ## The range is sparse /// /// Areal's 118 CEPs are scattered across the ~4,900 numbers between `25845001` /// and `25849899`. Never present the pair as a list of valid CEPs, and never /// count `cep_fim - cep_ini`: `ceps` is the count, and it is here so that /// mistake cannot be made by accident. /// /// ## Ownership /// /// Owns nothing and has no `deinit`. Its strings borrow the buffer they were /// packed into — the `Diagnostics` that read them — and are valid for exactly /// as long as that value is. Copy them if you need them to outlive it. pub const Faixa = struct { /// Municipality: `"Areal"`. localidade: []const u8 = "", /// Federative unit, two letters: `"RJ"`. uf: []const u8 = "", /// Federative unit spelled out: `"Rio de Janeiro"`. estado: []const u8 = "", /// Region: `"Sudeste"`. regiao: []const u8 = "", /// IBGE municipality code — the key to join with public datasets. ibge: []const u8 = "", /// The **smallest CEP that exists** in the municipality, not the start of /// the band the Correios published for it. cep_ini: []const u8 = "", /// The **largest CEP that exists** in the municipality. cep_fim: []const u8 = "", /// How many CEPs the municipality actually has between `cep_ini` and /// `cep_fim`. Almost always far fewer than the numbers those two span. ceps: u64 = 0, /// The string fields, in the order they are packed into the buffer. Used to /// keep the field list and the JSON keys in step; `localidade` must stay /// first, since it is the one that decides the block says anything at all. pub const text_fields = [_][]const u8{ "localidade", "uf", "estado", "regiao", "ibge", "cep_ini", "cep_fim", }; /// Bytes of storage a `Faixa` can need. The longest municipality name the /// DNE holds is 72 bytes and every other field is short, so this has room /// to spare — see `fromJson` for what happens if it somehow does not. pub const max_bytes = 256; /// Read the `faixa` block off the JSON body of a 404, packing its strings /// into `buffer` in `text_fields` order. /// /// Returns null — never an error — when the block is missing, null, not an /// object, carries no `localidade`, or would not fit in `buffer`. The /// answer is already `error.NotFound`, and no shape of this field may turn /// that into a failure to parse. The API **omits** `faixa` rather than /// sending null when the CEP falls in a band no municipality owns, so /// absent is the common case, not a malformed one. /// /// Nothing is allocated. The result borrows `buffer`, which must be at /// least `max_bytes` long to be sure of holding any real range. pub fn fromJson(value: std.json.Value, buffer: []u8) ?Faixa { const object = switch (value) { .object => |o| o, else => return null, }; const block = switch (object.get("faixa") orelse return null) { .object => |o| o, else => return null, }; const wrapped: std.json.Value = .{ .object = block }; var scratch: [text_fields.len][32]u8 = undefined; var values: [text_fields.len][]const u8 = undefined; var total: usize = 0; inline for (text_fields, 0..) |name, i| { values[i] = jsonText(wrapped, name, &scratch[i]); total += values[i].len; } // Without the municipality the block names nothing, which is the same // as not being there. if (values[0].len == 0) return null; if (total > buffer.len) return null; var faixa: Faixa = .{ .ceps = jsonCount(block.get("ceps")) }; var cursor: usize = 0; inline for (text_fields, 0..) |name, i| { const v = values[i]; @memcpy(buffer[cursor..][0..v.len], v); @field(faixa, name) = buffer[cursor..][0..v.len]; cursor += v.len; } return faixa; } }; /// The response of `GET /health`. /// /// Good for two things: checking that the API answers, and knowing **which /// version of the database** is live. The DNE database changes every two weeks; /// `dne` identifies it. /// /// ## Ownership /// /// Owns its strings. Call `deinit` with the allocator the `Client` was built /// with exactly once. pub const Health = struct { /// `"ok"` when the API considers itself healthy. status: []const u8 = "", /// Identifier of the fortnightly DNE database currently loaded. dne: []const u8 = "", /// How many CEPs the database holds. ceps: u64 = 0, /// How many of those carry a coordinate. geo: u64 = 0, /// When the database was generated, RFC 3339 in UTC. base_gerada_em: []const u8 = "", /// The response body exactly as it arrived. raw: []const u8 = "", /// Backing storage for the string fields. Internal. bytes: []u8 = &.{}, /// Release everything the value owns. Pass the allocator the `Client` was /// built with. pub fn deinit(h: *Health, gpa: Allocator) void { gpa.free(h.bytes); gpa.free(h.raw); h.* = undefined; } /// True when the API reports itself healthy. pub fn ok(h: Health) bool { return std.mem.eql(u8, h.status, "ok"); } /// Build from the JSON object the API returned. `body` is duplicated into /// `raw`; the returned value owns its memory — call `deinit`. pub fn fromJson(gpa: Allocator, value: std.json.Value, body: []const u8) Allocator.Error!Health { var scratch: [3][32]u8 = undefined; const status = jsonText(value, "status", &scratch[0]); const dne = jsonText(value, "dne", &scratch[1]); const base_gerada_em = jsonText(value, "base_gerada_em", &scratch[2]); const bytes = try gpa.alloc(u8, status.len + dne.len + base_gerada_em.len); errdefer gpa.free(bytes); const raw = try gpa.dupe(u8, body); errdefer gpa.free(raw); @memcpy(bytes[0..status.len], status); @memcpy(bytes[status.len..][0..dne.len], dne); @memcpy(bytes[status.len + dne.len ..][0..base_gerada_em.len], base_gerada_em); const maybe_object: ?std.json.ObjectMap = switch (value) { .object => |o| o, else => null, }; return .{ .status = bytes[0..status.len], .dne = bytes[status.len..][0..dne.len], .base_gerada_em = bytes[status.len + dne.len ..][0..base_gerada_em.len], .ceps = if (maybe_object) |o| jsonCount(o.get("ceps")) else 0, .geo = if (maybe_object) |o| jsonCount(o.get("geo")) else 0, .raw = raw, .bytes = bytes, }; } }; /// Read `key` off a JSON object as text. /// /// A string comes through as-is; a number is rendered into `scratch`; anything /// else — including a missing key and an explicit null — is the empty string, /// which is what the API sends for a field the database lacks. fn jsonText(value: std.json.Value, key: []const u8, scratch: []u8) []const u8 { const object = switch (value) { .object => |o| o, else => return "", }; return switch (object.get(key) orelse return "") { .string, .number_string => |s| s, .integer => |n| std.fmt.bufPrint(scratch, "{d}", .{n}) catch "", .float => |n| std.fmt.bufPrint(scratch, "{d}", .{n}) catch "", .bool => |b| if (b) "true" else "false", else => "", }; } fn jsonFloat(maybe: ?std.json.Value) ?f64 { return switch (maybe orelse return null) { .float => |n| n, .integer => |n| @floatFromInt(n), .number_string, .string => |s| std.fmt.parseFloat(f64, s) catch null, else => null, }; } fn jsonInt(maybe: ?std.json.Value) ?i64 { return switch (maybe orelse return null) { .integer => |n| n, .float => |n| @intFromFloat(n), .number_string, .string => |s| std.fmt.parseInt(i64, s, 10) catch null, else => null, }; } fn jsonCount(maybe: ?std.json.Value) u64 { return switch (maybe orelse return 0) { .integer => |n| if (n > 0) @intCast(n) else 0, .float => |n| if (n > 0) @intFromFloat(n) else 0, .number_string, .string => |s| std.fmt.parseInt(u64, s, 10) catch 0, else => 0, }; } const testing = std.testing; fn parseValue(body: []const u8) !std.json.Parsed(std.json.Value) { return std.json.parseFromSlice(std.json.Value, testing.allocator, body, .{}); } test "Address.fromJson reads every field and owns its strings" { const body = \\{"cep":"01001000","logradouro":"Praça da Sé","complemento":"lado ímpar", \\ "unidade":"","bairro":"Sé","localidade":"São Paulo","uf":"SP", \\ "estado":"São Paulo","regiao":"Sudeste","ibge":"3550308","gia":"1004", \\ "ddd":"11","siafi":"7107"} ; const parsed = try parseValue(body); defer parsed.deinit(); var address = try Address.fromJson(testing.allocator, parsed.value, body); defer address.deinit(testing.allocator); try testing.expectEqualStrings("01001000", address.cep); try testing.expectEqualStrings("Praça da Sé", address.logradouro); try testing.expectEqualStrings("São Paulo", address.localidade); try testing.expectEqualStrings("SP", address.uf); try testing.expectEqualStrings("7107", address.siafi); try testing.expectEqualStrings("", address.unidade); try testing.expect(address.geo == null); try testing.expectEqualStrings(body, address.raw); var buffer: [16]u8 = undefined; try testing.expectEqualStrings("01001-000", address.formattedCep(&buffer)); } test "Address.fromJson reads a geo block" { const body = \\{"cep":"01310100","geo":{"lat":-23.567202,"lon":-46.649659, \\ "raio_m":1084,"precisao":"logradouro"}} ; const parsed = try parseValue(body); defer parsed.deinit(); var address = try Address.fromJson(testing.allocator, parsed.value, body); defer address.deinit(testing.allocator); const geo = address.geo.?; try testing.expectApproxEqAbs(@as(f64, -23.567202), geo.lat, 1e-9); try testing.expectApproxEqAbs(@as(f64, -46.649659), geo.lon, 1e-9); try testing.expectEqual(@as(i64, 1084), geo.raio_m); try testing.expect(geo.isPrecise()); } test "a municipality coordinate is not precise" { const body = \\{"cep":"69900000","geo":{"lat":-9.9,"lon":-67.8,"raio_m":22000, \\ "precisao":"localidade"}} ; const parsed = try parseValue(body); defer parsed.deinit(); var address = try Address.fromJson(testing.allocator, parsed.value, body); defer address.deinit(testing.allocator); try testing.expect(!address.geo.?.isPrecise()); } test "a malformed geo block does not lose the address" { const body = \\{"cep":"01001000","localidade":"São Paulo","geo":{"lat":"not a number"}} ; const parsed = try parseValue(body); defer parsed.deinit(); var address = try Address.fromJson(testing.allocator, parsed.value, body); defer address.deinit(testing.allocator); try testing.expect(address.geo == null); try testing.expectEqualStrings("São Paulo", address.localidade); } test "an unknown field survives in raw and breaks nothing" { const body = \\{"cep":"01001000","localidade":"São Paulo","campo_do_futuro":"value"} ; const parsed = try parseValue(body); defer parsed.deinit(); var address = try Address.fromJson(testing.allocator, parsed.value, body); defer address.deinit(testing.allocator); try testing.expectEqualStrings("São Paulo", address.localidade); try testing.expect(std.mem.indexOf(u8, address.raw, "campo_do_futuro") != null); } test "a numeric field is rendered as text" { const body = \\{"cep":"01001000","ibge":3550308,"gia":null} ; const parsed = try parseValue(body); defer parsed.deinit(); var address = try Address.fromJson(testing.allocator, parsed.value, body); defer address.deinit(testing.allocator); try testing.expectEqualStrings("3550308", address.ibge); try testing.expectEqualStrings("", address.gia); } test "Address.fromCsvRecord maps by header and carries no geo" { const header = [_][]const u8{ "cep", "logradouro", "localidade", "uf", "coluna_nova" }; const record = [_][]const u8{ "01001000", "Praça da Sé", "São Paulo", "SP", "ignored" }; var address = try Address.fromCsvRecord(testing.allocator, &header, &record); defer address.deinit(testing.allocator); try testing.expectEqualStrings("01001000", address.cep); try testing.expectEqualStrings("Praça da Sé", address.logradouro); try testing.expectEqualStrings("SP", address.uf); try testing.expectEqualStrings("", address.siafi); try testing.expect(address.geo == null); try testing.expectEqualStrings("", address.raw); } test "formattedCep passes a malformed CEP through" { var address: Address = .{ .cep = "123" }; var buffer: [16]u8 = undefined; try testing.expectEqualStrings("123", address.formattedCep(&buffer)); } const areal_404 = \\{"erro":"cep_nao_encontrado","mensagem":"CEP não existe na base do DNE", \\ "faixa":{"localidade":"Areal","uf":"RJ","estado":"Rio de Janeiro", \\ "regiao":"Sudeste","ibge":"3300225","cep_ini":"25845001", \\ "cep_fim":"25849899","ceps":118}} ; test "Faixa.fromJson names the municipality of a CEP that does not exist" { const parsed = try parseValue(areal_404); defer parsed.deinit(); var buffer: [Faixa.max_bytes]u8 = undefined; const faixa = Faixa.fromJson(parsed.value, &buffer).?; try testing.expectEqualStrings("Areal", faixa.localidade); try testing.expectEqualStrings("RJ", faixa.uf); try testing.expectEqualStrings("Rio de Janeiro", faixa.estado); try testing.expectEqualStrings("Sudeste", faixa.regiao); try testing.expectEqualStrings("3300225", faixa.ibge); // The DNE's own start for Areal is 25845000; cep_ini is the smallest CEP // that exists, which is not the same number. try testing.expectEqualStrings("25845001", faixa.cep_ini); try testing.expectEqualStrings("25849899", faixa.cep_fim); // 118 CEPs over the ~4,900 numbers the pair spans: the range is sparse. try testing.expectEqual(@as(u64, 118), faixa.ceps); } test "a faixa the API omitted is null, not an error" { const bodies = [_][]const u8{ \\{"erro":"cep_nao_encontrado","mensagem":"CEP não existe na base do DNE"} , \\{"erro":"cep_nao_encontrado","faixa":null} , \\{"erro":"cep_nao_encontrado","faixa":{}} , \\{"erro":"cep_nao_encontrado","faixa":"Areal"} , \\{"erro":"cep_nao_encontrado","faixa":{"uf":"RJ","ceps":118}} , \\{"erro":"cep_nao_encontrado","faixa":{"localidade":"","uf":"RJ"}} , \\[] , }; for (bodies) |body| { const parsed = try parseValue(body); defer parsed.deinit(); var buffer: [Faixa.max_bytes]u8 = undefined; try testing.expect(Faixa.fromJson(parsed.value, &buffer) == null); } } test "a partial faixa keeps what it has" { const body = \\{"faixa":{"localidade":"Areal","ceps":"118"}} ; const parsed = try parseValue(body); defer parsed.deinit(); var buffer: [Faixa.max_bytes]u8 = undefined; const faixa = Faixa.fromJson(parsed.value, &buffer).?; try testing.expectEqualStrings("Areal", faixa.localidade); try testing.expectEqualStrings("", faixa.uf); try testing.expectEqualStrings("", faixa.cep_ini); try testing.expectEqual(@as(u64, 118), faixa.ceps); } test "a faixa that does not fit is reported as absent, not truncated" { const body = \\{"faixa":{"localidade":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","uf":"RJ"}} ; const parsed = try parseValue(body); defer parsed.deinit(); var small: [8]u8 = undefined; try testing.expect(Faixa.fromJson(parsed.value, &small) == null); } test "Health.fromJson reads counts and status" { const body = \\{"status":"ok","dne":"26071","ceps":1596168,"geo":894489, \\ "base_gerada_em":"2026-07-24T03:48:28Z"} ; const parsed = try parseValue(body); defer parsed.deinit(); var health = try Health.fromJson(testing.allocator, parsed.value, body); defer health.deinit(testing.allocator); try testing.expect(health.ok()); try testing.expectEqualStrings("26071", health.dne); try testing.expectEqual(@as(u64, 1_596_168), health.ceps); try testing.expectEqual(@as(u64, 894_489), health.geo); try testing.expectEqualStrings("2026-07-24T03:48:28Z", health.base_gerada_em); } test "a degraded Health is still a Health" { const body = \\{"status":"degradado"} ; const parsed = try parseValue(body); defer parsed.deinit(); var health = try Health.fromJson(testing.allocator, parsed.value, body); defer health.deinit(testing.allocator); try testing.expect(!health.ok()); try testing.expectEqual(@as(u64, 0), health.ceps); }