瀏覽代碼

RHL-007-feat(api): implement standardized error handling for filesystem/storage errors

Code_Uwe 9 小時之前
父節點
當前提交
d1e5e546fa
共有 1 個文件被更改,包括 109 次插入0 次删除
  1. 109 0
      lib/api/storageErrors.js

+ 109 - 0
lib/api/storageErrors.js

@@ -0,0 +1,109 @@
+/**
+ * Map filesystem/storage errors into standardized ApiErrors.
+ *
+ * Problem:
+ * - `fs.readdir()` throws ENOENT when a path does not exist.
+ * - Without mapping, routes often return 500 with `error.message` (inconsistent).
+ *
+ * Goal:
+ * - If a requested branch/year/month/day path does not exist => return 404.
+ * - But if the NAS root itself is not available/misconfigured => return 500.
+ *
+ * This avoids:
+ * - Incorrect 500s for normal “not found” cases.
+ * - Misleading 404s when the whole NAS is down.
+ */
+
+import fs from "node:fs/promises";
+import { ApiError } from "./errors.js";
+
+/**
+ * Check whether an error looks like a “path not found” error from Node’s fs.
+ * ENOENT: No such file or directory
+ * ENOTDIR: A path segment is not a directory
+ *
+ * @param {unknown} err
+ * @returns {boolean}
+ */
+export function isFsNotFoundError(err) {
+	return Boolean(
+		err &&
+			typeof err === "object" &&
+			(err.code === "ENOENT" || err.code === "ENOTDIR")
+	);
+}
+
+/**
+ * Determine if the configured NAS root path is accessible.
+ *
+ * Rationale:
+ * - If NAS_ROOT_PATH is missing or unreachable, “not found” below it
+ *   is likely a system issue => return 500 instead of 404.
+ *
+ * @returns {Promise<boolean>}
+ */
+async function isNasRootAccessible() {
+	const root = process.env.NAS_ROOT_PATH;
+	if (!root) return false;
+
+	try {
+		// access() succeeds if the path exists and is accessible.
+		await fs.access(root);
+		return true;
+	} catch {
+		return false;
+	}
+}
+
+/**
+ * Convert errors from the storage layer into ApiErrors.
+ *
+ * Policy:
+ * - ENOENT/ENOTDIR:
+ *     - If NAS root accessible => 404 FS_NOT_FOUND (requested resource missing)
+ *     - Else => 500 FS_STORAGE_ERROR (system/config issue)
+ * - Anything else => 500 FS_STORAGE_ERROR
+ *
+ * @param {unknown} err
+ * @param {{
+ *   notFoundCode?: string,
+ *   notFoundMessage?: string,
+ *   details?: any
+ * }=} options
+ * @returns {Promise<ApiError>}
+ */
+export async function mapStorageReadError(
+	err,
+	{ notFoundCode = "FS_NOT_FOUND", notFoundMessage = "Not found", details } = {}
+) {
+	if (isFsNotFoundError(err)) {
+		const rootOk = await isNasRootAccessible();
+
+		// If the NAS is not accessible, treat it as an internal failure.
+		if (!rootOk) {
+			return new ApiError({
+				status: 500,
+				code: "FS_STORAGE_ERROR",
+				message: "Internal server error",
+				cause: err,
+			});
+		}
+
+		// NAS root exists => the specific requested path is missing => 404.
+		return new ApiError({
+			status: 404,
+			code: notFoundCode,
+			message: notFoundMessage,
+			details,
+			cause: err,
+		});
+	}
+
+	// For all other filesystem errors, return a generic 500.
+	return new ApiError({
+		status: 500,
+		code: "FS_STORAGE_ERROR",
+		message: "Internal server error",
+		cause: err,
+	});
+}