/** * Exception hierarchy. * * Everything derives from $(D CepisException), so callers who only care that * $(I something) went wrong can catch one type. * * Below that, the split is by $(B the decision the caller has to make), not by * the HTTP status that caused it: * * $(UL * $(LI $(D InvalidCepException) and $(D CepNotFoundException) are problems * with the $(I request). Retrying is pointless — the same CEP will fail * again.) * $(LI $(D NetworkException), $(D ServerException) and * $(D RateLimitedException) are problems with $(I transport). Retrying * may help, and the client already retried on its own before throwing * any of them.) * ) * * That split is why $(D CepNotFoundException) is not "the HTTP 404 exception": * the client also throws $(D InvalidCepException) without making any request at * all, when the CEP is malformed before it ever leaves the machine. */ module cepis.exception; import core.time : Duration; import std.typecons : Nullable; @safe: /** * Base of every error this library throws. Catch this to handle any failure. * * Also thrown directly for a configuration mistake — a $(D ClientConfig) that * can only produce a client which never completes a call. */ class CepisException : Exception { /// this(string message, string file = __FILE__, size_t line = __LINE__) pure nothrow { super(message, file, line); } /** * Whether trying the same call again later could plausibly succeed. * * True for $(D RateLimitedException), $(D ServerException) and * $(D NetworkException); false for a bad or unknown CEP, which fails the * same way forever. * * The client has already retried internally by the time any of these are * thrown, so "later" means later — not immediately. */ bool retriable() const pure nothrow @nogc { return false; } } /** * The CEP is not 8 digits. * * Usually thrown $(B 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, in which case the message is the API's own. */ class InvalidCepException : CepisException { /// this(string message, string file = __FILE__, size_t line = __LINE__) pure nothrow { super(message, file, line); } } /// The CEP is well-formed but does not exist in the DNE database. class CepNotFoundException : CepisException { /// The CEP that was looked up, unmasked. const string cep; /// this(string cep, string file = __FILE__, size_t line = __LINE__) pure nothrow { super("CEP " ~ cep ~ " does not exist in the DNE database", file, line); this.cep = cep; } } /// The API (or the edge in front of it) answered 429 on every attempt. class RateLimitedException : CepisException { /** * What the server asked for via $(D Retry-After), or null if it did not * say. * * The client already honoured it across the attempts it made; it is exposed * here for callers who want to reschedule the work. */ const Nullable!Duration retryAfter; /// this(string message, Nullable!Duration retryAfter, string file = __FILE__, size_t line = __LINE__) pure nothrow { super(message, file, line); this.retryAfter = retryAfter; } override bool retriable() const pure nothrow @nogc { return true; } } /// The API answered 5xx on every attempt. class ServerException : CepisException { /// The HTTP status seen on the last attempt. const ushort status; /// this(string message, ushort status, string file = __FILE__, size_t line = __LINE__) pure nothrow { super(message, file, line); this.status = status; } override bool retriable() const pure nothrow @nogc { return true; } } /** * The request never completed: DNS, refused connection, timeout, TLS. * * Thrown only after every configured attempt has failed. */ class NetworkException : CepisException { /// this(string message, string file = __FILE__, size_t line = __LINE__) pure nothrow { super(message, file, line); } override bool retriable() const pure nothrow @nogc { return true; } } /** * 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 InvalidResponseException : CepisException { /// this(string message, string file = __FILE__, size_t line = __LINE__) pure nothrow { super(message, file, line); } }