summaryrefslogtreecommitdiffstats
path: root/src/Cepis/FaixaReader.cs
blob: da3c6a278e410467ad01c0cd66f33888d142e01a (plain)
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
using System.Globalization;
using System.Text.Json;

namespace Cepis;

/// <summary>
/// Reads the <c>faixa</c> block of a 404 body, yielding <see langword="null"/>
/// for anything it cannot make sense of.
/// </summary>
/// <remarks>
/// <para>
/// A reader rather than a <see cref="System.Text.Json.Serialization.JsonConverter{T}"/>
/// because <c>faixa</c> is not a field of any model this library deserializes:
/// it hangs off an error body that is otherwise only read for its message.
/// </para>
/// <para>
/// Nothing in here throws. The block is a courtesy on top of a 404 — a missing,
/// misshapen or half-empty one must leave the caller with the same clean
/// <see cref="CepNotFoundException"/> it would have got before the API grew the
/// field, never with a parse failure on top of a not-found.
/// </para>
/// </remarks>
internal static class FaixaReader
{
    /// <summary>Turn the <c>faixa</c> element into a range, or null.</summary>
    /// <param name="element">the block, as it came off the wire</param>
    /// <returns>the range, or null when there is nothing usable in it</returns>
    internal static Faixa? Read(JsonElement element)
    {
        if (element.ValueKind != JsonValueKind.Object)
        {
            return null;
        }

        var localidade = ReadString(element, "localidade");
        if (string.IsNullOrWhiteSpace(localidade))
        {
            return null;
        }

        return new Faixa
        {
            Localidade = localidade,
            UF = ReadString(element, "uf"),
            Estado = ReadString(element, "estado"),
            Regiao = ReadString(element, "regiao"),
            Ibge = ReadString(element, "ibge"),
            CepIni = ReadString(element, "cep_ini"),
            CepFim = ReadString(element, "cep_fim"),
            Ceps = ReadInt(element, "ceps"),
        };
    }

    /// <summary>
    /// The property as text, or the empty string when it is absent or of a
    /// shape that is not text.
    /// </summary>
    /// <remarks>
    /// A number is accepted and taken verbatim, the way
    /// <see cref="LenientStringConverter"/> does it for the address fields.
    /// </remarks>
    private static string ReadString(JsonElement owner, string name)
    {
        if (!owner.TryGetProperty(name, out var element))
        {
            return "";
        }

        return element.ValueKind switch
        {
            JsonValueKind.String => element.GetString() ?? "",
            JsonValueKind.Number => element.GetRawText(),
            _ => "",
        };
    }

    /// <summary>The property as a count, or zero when it cannot be read.</summary>
    private static int ReadInt(JsonElement owner, string name)
    {
        if (!owner.TryGetProperty(name, out var element))
        {
            return 0;
        }

        return element.ValueKind switch
        {
            JsonValueKind.Number => element.TryGetInt32(out var number) ? number : 0,
            JsonValueKind.String => int.TryParse(
                element.GetString(),
                NumberStyles.Integer,
                CultureInfo.InvariantCulture,
                out var parsed)
                ? parsed
                : 0,
            _ => 0,
        };
    }
}