1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
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;
}
}
|