summaryrefslogtreecommitdiffstats
path: root/include/cepis/errors.hpp
blob: fa928f03a482e96186af60eab942a4f6131c1b85 (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
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