summaryrefslogtreecommitdiffstats
path: root/source/cepis/exception.d
diff options
context:
space:
mode:
Diffstat (limited to 'source/cepis/exception.d')
-rw-r--r--source/cepis/exception.d168
1 files changed, 168 insertions, 0 deletions
diff --git a/source/cepis/exception.d b/source/cepis/exception.d
new file mode 100644
index 0000000..9b77449
--- /dev/null
+++ b/source/cepis/exception.d
@@ -0,0 +1,168 @@
+/**
+ * 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);
+ }
+}