Firebase Functions の HttpsError
こいつです!!
こいつを、 Firebase 非依存プロジェクトでも使いたいと思ったわけです。
どうして優れているの
すげー使いやすいAPIなんですよね。。。
app.get("/items/:id", async (req, res, next) => {
const item = await Item.find(id);
if (item == null) {
// ⭐ こいつ!
throw new HttpsError("not-found", "Invalid Item ID", detail);
}
...
});
- 第1引数が
404
といったhttpエラー番号でなく、直感的なtext.もちろん型定義されているのでサジェストされる。 - 第2引数に概要を書くことができる
- 第3引数に詳細をつめることができる
シンプルかつ洗練されたインターフェース。
express との連携例
こんな感じの middleware 定義して運用してるんですけどね。
middlewares.ts
export class APIMiddlewares {
static errorHandler(
err: Error | HttpsError,
req: Request,
res: Response,
next: NextFunction
) {
// ⭐ HttpsError はサービスで想定されたエラーとして処理
if (err.constructor.name === HttpsError.name) {
const httpsError = err as HttpsError;
res.status(httpsError.httpErrorCode.status).send({
status: httpsError.httpErrorCode.canonicalName,
msg: httpsError.message,
});
...
return;
}
// ⭐ HttpsError でないなら想定外のエラーとして処理
res.status(500).send({ status: "INTERNAL_SERVER_ERROR" });
...
}
}
想定されたものかどうか、色分けができる(そしてそれが優れたインターフェースのErrorを介してできる)だけでもめっちゃ便利。
パクってみた
https://github.com/firebase/firebase-functions/blob/98bf467aa436f7e27969a8ad3887abe0345f66d3/src/providers/https.ts
冒頭にも参照したソースから丸パクリ。 MITライセンスであることに注意
http-error.ts
export type FunctionsErrorCode =
| "ok"
| "cancelled"
| "unknown"
| "invalid-argument"
| "deadline-exceeded"
| "not-found"
| "already-exists"
| "permission-denied"
| "resource-exhausted"
| "failed-precondition"
| "aborted"
| "out-of-range"
| "unimplemented"
| "internal"
| "unavailable"
| "data-loss"
| "unauthenticated";
/** @hidden */
export type CanonicalErrorCodeName =
| "OK"
| "CANCELLED"
| "UNKNOWN"
| "INVALID_ARGUMENT"
| "DEADLINE_EXCEEDED"
| "NOT_FOUND"
| "ALREADY_EXISTS"
| "PERMISSION_DENIED"
| "UNAUTHENTICATED"
| "RESOURCE_EXHAUSTED"
| "FAILED_PRECONDITION"
| "ABORTED"
| "OUT_OF_RANGE"
| "UNIMPLEMENTED"
| "INTERNAL"
| "UNAVAILABLE"
| "DATA_LOSS";
/** @hidden */
interface HttpErrorCode {
canonicalName: CanonicalErrorCodeName;
status: number;
}
/**
* Standard error codes and HTTP statuses for different ways a request can fail,
* as defined by:
* https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
*
* This map is used primarily to convert from a client error code string to
* to the HTTP format error code string and status, and make sure it's in the
* supported set.
*/
const errorCodeMap: { [name in FunctionsErrorCode]: HttpErrorCode } = {
ok: { canonicalName: "OK", status: 200 },
cancelled: { canonicalName: "CANCELLED", status: 499 },
unknown: { canonicalName: "UNKNOWN", status: 500 },
"invalid-argument": { canonicalName: "INVALID_ARGUMENT", status: 400 },
"deadline-exceeded": { canonicalName: "DEADLINE_EXCEEDED", status: 504 },
"not-found": { canonicalName: "NOT_FOUND", status: 404 },
"already-exists": { canonicalName: "ALREADY_EXISTS", status: 409 },
"permission-denied": { canonicalName: "PERMISSION_DENIED", status: 403 },
unauthenticated: { canonicalName: "UNAUTHENTICATED", status: 401 },
"resource-exhausted": { canonicalName: "RESOURCE_EXHAUSTED", status: 429 },
"failed-precondition": { canonicalName: "FAILED_PRECONDITION", status: 400 },
aborted: { canonicalName: "ABORTED", status: 409 },
"out-of-range": { canonicalName: "OUT_OF_RANGE", status: 400 },
unimplemented: { canonicalName: "UNIMPLEMENTED", status: 501 },
internal: { canonicalName: "INTERNAL", status: 500 },
unavailable: { canonicalName: "UNAVAILABLE", status: 503 },
"data-loss": { canonicalName: "DATA_LOSS", status: 500 },
};
/** @hidden */
interface HttpErrorWireFormat {
details?: unknown;
message: string;
status: CanonicalErrorCodeName;
}
/**
* An explicit error that can be thrown from a handler to send an error to the
* client that called the function.
*/
export class HttpsError extends Error {
/**
* A standard error code that will be returned to the client. This also
* determines the HTTP status code of the response, as defined in code.proto.
*/
public readonly code: FunctionsErrorCode;
/**
* Extra data to be converted to JSON and included in the error response.
*/
public readonly details: unknown;
/**
* A wire format representation of a provided error code.
*
* @hidden
*/
public readonly httpErrorCode: HttpErrorCode;
constructor(code: FunctionsErrorCode, message: string, details?: unknown) {
super(message);
// A sanity check for non-TypeScript consumers.
if (code in errorCodeMap === false) {
throw new Error(`Unknown error code: ${code}.`);
}
this.code = code;
this.details = details;
this.httpErrorCode = errorCodeMap[code];
}
/** @hidden */
public toJSON(): HttpErrorWireFormat {
const {
details,
httpErrorCode: { canonicalName: status },
message,
} = this;
return {
...(details === undefined ? {} : { details }),
message,
status,
};
}
}
ありがとう Google, ありがとう Firebase.