summaryrefslogtreecommitdiffstats
path: root/source/cepis/package.d
blob: 9a8c433f4e8a7aeb366944198e17d6fba1f7d6f4 (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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
/**
 * D client for the public CEP API at $(LINK https://cep.is). Official Correios
 * data (DNE), refreshed every two weeks.
 *
 * Quick use — for a script, the module-level functions are enough:
 *
 * ---
 * import cepis;
 *
 * auto address = lookup("01001-000");
 * writeln(address.localidade, " ", address.uf);   // São Paulo SP
 * ---
 *
 * In an application — build a $(D Client) once and reuse it:
 *
 * ---
 * ClientConfig config;
 * config.timeout = dur!"seconds"(5);
 * config.totalTimeout = dur!"seconds"(15);
 *
 * auto client = Client(config);
 * auto address = client.lookup("01001000");
 * ---
 *
 * Pointing at a local instance (the API repo brings one up with `just up`):
 *
 * ---
 * ClientConfig config;
 * config.baseUrl = "http://localhost:8080";
 * auto client = Client(config);
 * ---
 *
 * Errors are explicit and split by decision — whether retrying helps or not:
 *
 * ---
 * try
 *     render(lookup(cepFromForm));
 * catch (CepNotFoundException)
 *     askForAnotherCep();               // the CEP does not exist
 * catch (CepisException error)
 *     if (error.retriable) reschedule(); // network, server, rate limit
 * ---
 *
 * A not-found sometimes knows where the CEP $(I would) have been — see
 * $(D CepNotFoundException.faixa) and $(D Faixa). That is context for the
 * error, never an address.
 *
 * Note on field names: the API surface is English, but the $(B data fields keep
 * the API's own Portuguese names) ($(D logradouro), $(D bairro),
 * $(D localidade)). What you read in the JSON is what you access on the value.
 *
 * The module-level functions share one default client per thread, built on
 * first use. Anyone needing different settings should build their own
 * $(D Client).
 */
module cepis;

public import cepis.client;
public import cepis.exception;
public import cepis.model;

import std.typecons : Nullable;

@safe:

/**
 * Look up a CEP using the default configuration.
 *
 * Shorthand for `Client(ClientConfig.init).lookup(cep)`; see $(D Client.lookup).
 */
Address lookup(scope const(char)[] cep)
{
    return defaultClient().lookup(cep);
}

/**
 * Look up many CEPs in one batch, using the default configuration.
 *
 * Shorthand for $(D Client.lookupMany) — read that one, especially the warning
 * that batches carry no coordinate.
 */
Nullable!Address[] lookupMany(scope const(string)[] ceps)
{
    return defaultClient().lookupMany(ceps);
}

/**
 * Query the API's health using the default configuration.
 *
 * Shorthand for $(D Client.health).
 */
Health health()
{
    return defaultClient().health();
}

private Client defaultClient()
{
    static Client instance;
    static bool built;

    if (!built)
    {
        instance = Client(ClientConfig.init);
        built = true;
    }
    return instance;
}

@("the default client is built once and points at the public API")
unittest
{
    assert(defaultClient().baseUrl == DEFAULT_URL);
    assert(defaultClient().maxAttempts == 3);
}