#ifndef CEPIS_ERRORS_HPP #define CEPIS_ERRORS_HPP #include #include #include 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 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 retry_after() const noexcept { return retry_after_; } private: std::optional 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