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
|
#ifndef CEPIS_ERRORS_HPP
#define CEPIS_ERRORS_HPP
#include <optional>
#include <stdexcept>
#include <string>
#include <utility>
#include "cepis/models.hpp"
namespace cepis {
/// Base of every error this library throws. Catch it to handle any failure.
///
/// It derives from `std::runtime_error`, so a `catch (const std::exception&)`
/// already in place around your I/O keeps working without knowing this library
/// exists, and `what()` carries the message.
///
/// Below this the split is by **the decision the caller has to make**, not by
/// the HTTP status that caused it:
///
/// - `InvalidCep` and `CepNotFound` are problems with the *request*. Retrying
/// is pointless — the same CEP fails again.
/// - `NetworkError`, `ServerError` and `RateLimited` are problems with
/// *transport*. Retrying may help, and the client already retried on its own
/// before throwing any of them.
class Error : public std::runtime_error {
public:
explicit Error(const std::string& message) : std::runtime_error(message) {}
};
/// The CEP is not eight digits.
///
/// Usually thrown **without** a request ever being made: the client checks the
/// format before going to the network. It is also what a `400` from the API
/// becomes, carrying the API's own message — the format already passed local
/// validation there, so the server knows something this client does not.
class InvalidCep : public Error {
public:
using Error::Error;
};
/// The CEP is well-formed but does not exist in the DNE database.
///
/// The API often says **where it would have been** — see `faixa()`. That is
/// still a not-found: the extra context does not make the CEP deliverable, and
/// this remains the exception thrown for it.
class CepNotFound : public Error {
public:
using Error::Error;
CepNotFound(const std::string& message, std::optional<Faixa> faixa)
: Error(message), faixa_(std::move(faixa)) {}
/// The municipality the CEP would have belonged to, when the API names one.
///
/// `std::nullopt` when the CEP falls in a band no municipality owns — the
/// API **omits** the block there, it never sends null — and also whenever
/// the block cannot be read. Read `Faixa` before showing any of it: the
/// range it describes is sparse, and the CEP does not exist either way.
///
/// ```cpp
/// } catch (const cepis::CepNotFound& error) {
/// if (error.faixa()) {
/// std::cerr << "not found; did you mean somewhere in "
/// << error.faixa()->localidade << '/' << error.faixa()->uf << "?\n";
/// }
/// }
/// ```
[[nodiscard]] const std::optional<Faixa>& faixa() const noexcept { return faixa_; }
private:
std::optional<Faixa> faixa_;
};
/// The request never completed: DNS, refused connection, timeout, TLS.
///
/// Thrown only after every configured attempt has failed.
class NetworkError : public Error {
public:
using Error::Error;
};
/// The API answered 5xx on every attempt.
class ServerError : public Error {
public:
ServerError(const std::string& message, int status) : Error(message), status_(status) {}
/// The HTTP status seen on the last attempt.
[[nodiscard]] int status() const noexcept { return status_; }
private:
int status_;
};
/// The API (or the edge in front of it) answered 429 on every attempt.
class RateLimited : public Error {
public:
RateLimited(const std::string& message, std::optional<double> retry_after)
: Error(message), retry_after_(retry_after) {}
/// What the server asked for, in seconds, or `std::nullopt` if it did not
/// say. The client already honoured that value across the attempts it made;
/// it is exposed here for callers who want to reschedule the work.
[[nodiscard]] std::optional<double> retry_after() const noexcept { return retry_after_; }
private:
std::optional<double> retry_after_;
};
/// A success status came back with a body that cannot be parsed.
///
/// In practice this means something between you and the API is answering
/// instead of it — a captive Wi-Fi portal, a corporate proxy, an HTML error
/// page. Useful as a diagnosis: the problem is not in your code.
class InvalidResponse : public Error {
public:
using Error::Error;
};
} // namespace cepis
#endif // CEPIS_ERRORS_HPP
|