rea/src/cliJsonInput.ts

76 lines
2.3 KiB
TypeScript

import { readFile, stat } from "node:fs/promises";
import { AnalysisInputError, projectAnalysisError } from "./domain/errors.js";
import type { JsonValue } from "./domain/jsonValue.js";
const MAX_JSON_INPUT_BYTES = 64 * 1_024 * 1_024;
/** Parse bounded inline JSON or one bounded local JSON file for a CLI workflow. */
export const parseCliJsonInput = async (
value: string,
operation: string,
): Promise<
| { readonly ok: true; readonly value: unknown }
| { readonly ok: false; readonly error: JsonValue }
> => {
const inline = parseJson(value);
if (inline !== undefined) return { ok: true, value: inline };
if (["{", "["].includes(value.trimStart()[0] ?? ""))
return { ok: false, error: inputError(operation) };
try {
const metadata = await stat(value);
if (!metadata.isFile()) return jsonFileError(value, operation, "not-file");
if (metadata.size > MAX_JSON_INPUT_BYTES)
return jsonFileError(value, operation, "too-large");
const bytes = await readFile(value);
if (bytes.byteLength > MAX_JSON_INPUT_BYTES)
return jsonFileError(value, operation, "too-large");
const parsed = parseJson(bytes.toString("utf8"));
return parsed === undefined
? jsonFileError(value, operation, "invalid-json")
: { ok: true, value: parsed };
} catch {
return jsonFileError(value, operation, "read-failed");
}
};
const parseJson = (value: string): unknown => {
try {
return JSON.parse(value);
} catch {
return undefined;
}
};
const inputError = (operation: string): JsonValue => ({
error: "Application workflow failed",
...projectAnalysisError(
new AnalysisInputError(operation, undefined, [
{ path: [], reason: "invalid_format", expected: "JSON" },
]),
),
});
const jsonFileError = (
path: string,
operation: string,
reason: "not-file" | "too-large" | "invalid-json" | "read-failed",
) => ({
ok: false as const,
error: {
error: "Application workflow failed",
...projectAnalysisError(
new AnalysisInputError(
operation,
undefined,
reason === "invalid-json"
? [{ path: [], reason: "invalid_format", expected: "JSON" }]
: [],
),
),
input_path: path,
input_reason: reason,
maximum_input_bytes: MAX_JSON_INPUT_BYTES,
},
});