I’m working with an API that has some objectively poor DDOS Errors.
When the service is denied, it returns a 503 with this body:
<html>
<head><title>503 Service Temporarily Unavailable</title></head>
<body bgcolor="white">
<center><h1>503 Service Temporarily Unavailable</h1></center>
<hr><center>nginx/1.14.0 (Ubuntu)</center>
</body>
</html>
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
This results in an error that looks like this from rescript side
DAOSquare : 678414857510453309: {
RE_EXN_ID: 'Promise.JsError/2',
_1: FetchError: invalid json response body at https://app.brightid.org/node/v5/verifications/Discord/cd9b9f98-3190-517a-8d3d-d6c76f916cbc?timestamp=seconds reason: Unexpected token < in JSON at position 0
at /app/node_modules/node-fetch/lib/index.js:273:32
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async onGuildMemberUpdate (file:///app/apps/bot/src/Bot.mjs:549:19) {
type: 'invalid-json'
}
}
How can I catch this specific JSError? It would be nice to utilize FetchError from node-fetch if possible. Here is how I am logging the error.
I had a look at the node-fetch error docs and it looks like you can switch on the name. I copied the error code from node-fetch into a js file for an example, just to test it out.
(ThrowIt.js)
class FetchBaseError extends Error {
constructor(message, type) {
super(message);
Error.captureStackTrace(this, this.constructor);
this.type = type;
}
get name() {
return this.constructor.name;
}
get [Symbol.toStringTag]() {
return this.constructor.name;
}
}
class FetchError extends FetchBaseError {
constructor(message, type, systemError) {
super(message, type);
if (systemError) {
this.code = this.errno = systemError.code;
this.erroredSysCall = systemError.syscall;
}
}
}
class AbortError extends FetchBaseError {
constructor(message, type = "aborted") {
super(message, type);
}
}
exports.oops = (i) => {
if (i > 0) {
throw new FetchError("positive number");
} else if (i < 0) {
throw new AbortError("negative number");
} else {
throw new Error("zero");
}
};
Then a little example rescript (didn’t bother with async, but you get the idea). The Error module could be made specific for the node-fetch, or for all js exceptions, whatever you want to do. OrError module isn’t strictly necessary, just imagining a nice way to work with them.
@module("./ThrowIt")
external oops: int => unit = "oops"
module Error = {
// Model this however you want...you could even carry around the orig js exn
// if you want to reraise it.
type t = [
| #FetchError(option<string>)
| #AbortError(option<string>)
| #Error(option<string>)
| #Other(option<string>)
]
// Convert from js exn
let ofJsExn = jsExn => {
let msg = Js.Exn.message(jsExn)
switch Js.Exn.name(jsExn) {
| Some("FetchError") => #FetchError(msg)
| Some("AbortError") => #AbortError(msg)
| Some("Error") => #Error(msg)
| _ => #UnknownError(msg)
}
}
}
module OrError = {
type t<'a> = Result.t<'a, Error.t>
// Adjust for your async needs...
let try_with = f =>
try {
Ok(f())
} catch {
| Js.Exn.Error(obj) => Error(Error.ofJsExn(obj))
}
}
let oops = i => OrError.try_with(() => oops(i))
// Silly example of dealing with just the FetchError
exception Fail
let _ = switch oops(10) {
| Ok() => ()
| Error(#FetchError(_)) => Console.log("caught!")
| _ => raise(Fail)
}
// Handle errors as needed...
oops(1)->Console.log
oops(0)->Console.log
oops(-1)->Console.log
Of course, you can choose to model the errors however you want…polyvariants, variants, string, exn, whatever you like.