summaryrefslogtreecommitdiffstats
path: root/src/Cepis/Address.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Cepis/Address.cs')
-rw-r--r--src/Cepis/Address.cs205
1 files changed, 205 insertions, 0 deletions
diff --git a/src/Cepis/Address.cs b/src/Cepis/Address.cs
new file mode 100644
index 0000000..edc7776
--- /dev/null
+++ b/src/Cepis/Address.cs
@@ -0,0 +1,205 @@
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Cepis;
+
+/// <summary>
+/// A CEP and everything the database knows about it.
+/// </summary>
+/// <remarks>
+/// <para>
+/// 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
+/// <c>https://cep.is/01001000</c> is what you access on the object. The
+/// mapping from wire name to property is:
+/// </para>
+/// <list type="table">
+/// <listheader><term>wire</term><description>property</description></listheader>
+/// <item><term><c>cep</c></term><description><see cref="Cep"/></description></item>
+/// <item><term><c>logradouro</c></term><description><see cref="Logradouro"/></description></item>
+/// <item><term><c>complemento</c></term><description><see cref="Complemento"/></description></item>
+/// <item><term><c>unidade</c></term><description><see cref="Unidade"/></description></item>
+/// <item><term><c>bairro</c></term><description><see cref="Bairro"/></description></item>
+/// <item><term><c>localidade</c></term><description><see cref="Localidade"/></description></item>
+/// <item><term><c>uf</c></term><description><see cref="UF"/></description></item>
+/// <item><term><c>estado</c></term><description><see cref="Estado"/></description></item>
+/// <item><term><c>regiao</c></term><description><see cref="Regiao"/></description></item>
+/// <item><term><c>ibge</c></term><description><see cref="Ibge"/></description></item>
+/// <item><term><c>gia</c></term><description><see cref="Gia"/></description></item>
+/// <item><term><c>ddd</c></term><description><see cref="Ddd"/></description></item>
+/// <item><term><c>siafi</c></term><description><see cref="Siafi"/></description></item>
+/// <item><term><c>geo</c></term><description><see cref="Geo"/></description></item>
+/// </list>
+/// <para>
+/// Fields the database does not have for that CEP come back as empty strings,
+/// never <see langword="null"/> — that is what the API returns.
+/// </para>
+/// <para>
+/// <see cref="Geo"/> is the exception: it is <see langword="null"/> when there
+/// is no coordinate. There the difference is real, because the API <b>omits</b>
+/// the whole field in that case (about 43% of CEPs) rather than sending null.
+/// It is also always <see langword="null"/> for an address that came from
+/// <see cref="CepisClient.LookupManyAsync(IEnumerable{string}, CancellationToken)"/>,
+/// which carries no coordinate at all.
+/// </para>
+/// <para>
+/// <see cref="Extra"/> 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 <see cref="object.ToString"/>: two addresses
+/// describing the same place are equal even if one of them was built by hand.
+/// </para>
+/// </remarks>
+public sealed record Address
+{
+ /// <summary>The eight digits, always unmasked. Wire name <c>cep</c>.</summary>
+ [JsonPropertyName("cep")]
+ public string Cep { get; init; } = "";
+
+ /// <summary>The street. Wire name <c>logradouro</c>.</summary>
+ [JsonPropertyName("logradouro")]
+ public string Logradouro { get; init; } = "";
+
+ /// <summary>
+ /// The qualifier on the street, such as a range of numbers. Wire name
+ /// <c>complemento</c>.
+ /// </summary>
+ [JsonPropertyName("complemento")]
+ public string Complemento { get; init; } = "";
+
+ /// <summary>
+ /// The named unit, for a CEP that belongs to one. Wire name <c>unidade</c>.
+ /// </summary>
+ [JsonPropertyName("unidade")]
+ public string Unidade { get; init; } = "";
+
+ /// <summary>The neighbourhood. Wire name <c>bairro</c>.</summary>
+ [JsonPropertyName("bairro")]
+ public string Bairro { get; init; } = "";
+
+ /// <summary>The municipality. Wire name <c>localidade</c>.</summary>
+ [JsonPropertyName("localidade")]
+ public string Localidade { get; init; } = "";
+
+ /// <summary>The two-letter state code. Wire name <c>uf</c>.</summary>
+ [JsonPropertyName("uf")]
+ public string UF { get; init; } = "";
+
+ /// <summary>The state's name. Wire name <c>estado</c>.</summary>
+ [JsonPropertyName("estado")]
+ public string Estado { get; init; } = "";
+
+ /// <summary>The region of the country. Wire name <c>regiao</c>.</summary>
+ [JsonPropertyName("regiao")]
+ public string Regiao { get; init; } = "";
+
+ /// <summary>The IBGE municipality code. Wire name <c>ibge</c>.</summary>
+ [JsonPropertyName("ibge")]
+ public string Ibge { get; init; } = "";
+
+ /// <summary>The GIA code, São Paulo only. Wire name <c>gia</c>.</summary>
+ [JsonPropertyName("gia")]
+ public string Gia { get; init; } = "";
+
+ /// <summary>The telephone area code. Wire name <c>ddd</c>.</summary>
+ [JsonPropertyName("ddd")]
+ public string Ddd { get; init; } = "";
+
+ /// <summary>The SIAFI municipality code. Wire name <c>siafi</c>.</summary>
+ [JsonPropertyName("siafi")]
+ public string Siafi { get; init; } = "";
+
+ /// <summary>
+ /// The coordinate, or <see langword="null"/> when there is none. Wire name
+ /// <c>geo</c>.
+ /// </summary>
+ /// <remarks>
+ /// A malformed <c>geo</c> block yields <see langword="null"/> rather than
+ /// failing the whole response: the rest of the address is still useful.
+ /// </remarks>
+ [JsonPropertyName("geo")]
+ [JsonConverter(typeof(GeoConverter))]
+ public Geo? Geo { get; init; }
+
+ /// <summary>
+ /// Fields the API returned that this version of the library does not model.
+ /// </summary>
+ /// <remarks>
+ /// A new field in the API therefore cannot break this client, and cannot be
+ /// silently dropped either. Empty for an address built by hand.
+ /// </remarks>
+ [JsonExtensionData]
+ public Dictionary<string, JsonElement>? Extra { get; init; }
+
+ /// <summary>The CEP with its mask, <c>01001-000</c>.</summary>
+ /// <remarks>
+ /// 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.
+ /// </remarks>
+ [JsonIgnore]
+ public string FormattedCep => Cep.Length == 8 ? $"{Cep[..5]}-{Cep[5..]}" : Cep;
+
+ /// <summary>Compare every field but <see cref="Extra"/>.</summary>
+ /// <param name="other">the address to compare with</param>
+ /// <returns><see langword="true"/> when both describe the same place</returns>
+ 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;
+
+ /// <summary>Hash every field but <see cref="Extra"/>.</summary>
+ /// <returns>a hash consistent with <see cref="Equals(Address)"/></returns>
+ 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;
+ }
+}