using System.Text; using System.Text.Json; using System.Text.Json.Serialization; namespace Cepis; /// /// A CEP and everything the database knows about it. /// /// /// /// The property names are the API's own, in Portuguese, with only the casing /// adapted to C#. Everything else in this library is English; the data fields /// are the one deliberate exception, so that what you read in the JSON at /// https://cep.is/01001000 is what you access on the object. The /// mapping from wire name to property is: /// /// /// wireproperty /// cep /// logradouro /// complemento /// unidade /// bairro /// localidade /// uf /// estado /// regiao /// ibge /// gia /// ddd /// siafi /// geo /// /// /// Fields the database does not have for that CEP come back as empty strings, /// never — that is what the API returns. /// /// /// is the exception: it is 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. /// It is also always for an address that came from /// , /// which carries no coordinate at all. /// /// /// holds any field the API has grown that this version of /// the library does not model, so an outdated client loses nothing. It takes no /// part in equality, hashing or : two addresses /// describing the same place are equal even if one of them was built by hand. /// /// public sealed record Address { /// The eight digits, always unmasked. Wire name cep. [JsonPropertyName("cep")] public string Cep { get; init; } = ""; /// The street. Wire name logradouro. [JsonPropertyName("logradouro")] public string Logradouro { get; init; } = ""; /// /// The qualifier on the street, such as a range of numbers. Wire name /// complemento. /// [JsonPropertyName("complemento")] public string Complemento { get; init; } = ""; /// /// The named unit, for a CEP that belongs to one. Wire name unidade. /// [JsonPropertyName("unidade")] public string Unidade { get; init; } = ""; /// The neighbourhood. Wire name bairro. [JsonPropertyName("bairro")] public string Bairro { get; init; } = ""; /// The municipality. Wire name localidade. [JsonPropertyName("localidade")] public string Localidade { get; init; } = ""; /// The two-letter state code. Wire name uf. [JsonPropertyName("uf")] public string UF { get; init; } = ""; /// The state's name. Wire name estado. [JsonPropertyName("estado")] public string Estado { get; init; } = ""; /// The region of the country. Wire name regiao. [JsonPropertyName("regiao")] public string Regiao { get; init; } = ""; /// The IBGE municipality code. Wire name ibge. [JsonPropertyName("ibge")] public string Ibge { get; init; } = ""; /// The GIA code, São Paulo only. Wire name gia. [JsonPropertyName("gia")] public string Gia { get; init; } = ""; /// The telephone area code. Wire name ddd. [JsonPropertyName("ddd")] public string Ddd { get; init; } = ""; /// The SIAFI municipality code. Wire name siafi. [JsonPropertyName("siafi")] public string Siafi { get; init; } = ""; /// /// The coordinate, or when there is none. Wire name /// geo. /// /// /// A malformed geo block yields rather than /// failing the whole response: the rest of the address is still useful. /// [JsonPropertyName("geo")] [JsonConverter(typeof(GeoConverter))] public Geo? Geo { get; init; } /// /// Fields the API returned that this version of the library does not model. /// /// /// A new field in the API therefore cannot break this client, and cannot be /// silently dropped either. Empty for an address built by hand. /// [JsonExtensionData] public Dictionary? Extra { get; init; } /// The CEP with its mask, 01001-000. /// /// The API always returns it unmasked; formatting is presentation, and it /// lives here so it does not become a repeated interpolation in every /// project that uses this library. A CEP that is not eight characters long /// is returned unchanged. /// [JsonIgnore] public string FormattedCep => Cep.Length == 8 ? $"{Cep[..5]}-{Cep[5..]}" : Cep; /// Compare every field but . /// the address to compare with /// when both describe the same place public bool Equals(Address? other) => other is not null && Cep == other.Cep && Logradouro == other.Logradouro && Complemento == other.Complemento && Unidade == other.Unidade && Bairro == other.Bairro && Localidade == other.Localidade && UF == other.UF && Estado == other.Estado && Regiao == other.Regiao && Ibge == other.Ibge && Gia == other.Gia && Ddd == other.Ddd && Siafi == other.Siafi && Geo == other.Geo; /// Hash every field but . /// a hash consistent with public override int GetHashCode() { var hash = default(HashCode); hash.Add(Cep); hash.Add(Logradouro); hash.Add(Complemento); hash.Add(Unidade); hash.Add(Bairro); hash.Add(Localidade); hash.Add(UF); hash.Add(Estado); hash.Add(Regiao); hash.Add(Ibge); hash.Add(Gia); hash.Add(Ddd); hash.Add(Siafi); hash.Add(Geo); return hash.ToHashCode(); } private bool PrintMembers(StringBuilder builder) { builder.Append("Cep = ").Append(Cep) .Append(", Logradouro = ").Append(Logradouro) .Append(", Complemento = ").Append(Complemento) .Append(", Unidade = ").Append(Unidade) .Append(", Bairro = ").Append(Bairro) .Append(", Localidade = ").Append(Localidade) .Append(", UF = ").Append(UF) .Append(", Estado = ").Append(Estado) .Append(", Regiao = ").Append(Regiao) .Append(", Ibge = ").Append(Ibge) .Append(", Gia = ").Append(Gia) .Append(", Ddd = ").Append(Ddd) .Append(", Siafi = ").Append(Siafi) .Append(", Geo = ").Append(Geo?.ToString() ?? "null"); return true; } }