summaryrefslogtreecommitdiffstats
path: root/include/cepis/errors.hpp
diff options
context:
space:
mode:
Diffstat (limited to 'include/cepis/errors.hpp')
-rw-r--r--include/cepis/errors.hpp93
1 files changed, 93 insertions, 0 deletions
diff --git a/include/cepis/errors.hpp b/include/cepis/errors.hpp
new file mode 100644
index 0000000..0420ecd
--- /dev/null
+++ b/include/cepis/errors.hpp
@@ -0,0 +1,93 @@
+#ifndef CEPIS_ERRORS_HPP
+#define CEPIS_ERRORS_HPP
+
+#include <optional>
+#include <stdexcept>
+#include <string>
+
+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.
+class CepNotFound : public Error {
+ public:
+ using Error::Error;
+};
+
+/// 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