diff options
Diffstat (limited to 'include/cepis/errors.hpp')
| -rw-r--r-- | include/cepis/errors.hpp | 93 |
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 |