91 lines
3.1 KiB
TypeScript
91 lines
3.1 KiB
TypeScript
/**
|
|
* Checks the JSON bodies that `routes/dumps.ts` takes to post a link and to
|
|
* edit a dump. A file dump arrives as a multipart form instead, and
|
|
* `dump-service.ts` checks its title and comment.
|
|
*
|
|
* @module
|
|
*/
|
|
|
|
import type {
|
|
CreateUrlDumpRequest,
|
|
UpdateDumpRequest,
|
|
} from "../../shared/dump.ts";
|
|
import { VALIDATION } from "../../shared/validation.ts";
|
|
|
|
function isStringArray(value: unknown): value is string[] {
|
|
return Array.isArray(value) && value.every((v) => typeof v === "string");
|
|
}
|
|
|
|
/**
|
|
* Checks whether `obj` is a {@link CreateUrlDumpRequest} whose title and
|
|
* comment fit the `VALIDATION` limits. It leaves the URL to `createUrlDump`,
|
|
* which rejects anything but a public http(s) URL.
|
|
*/
|
|
export function isCreateUrlDumpRequest(
|
|
obj: unknown,
|
|
): obj is CreateUrlDumpRequest {
|
|
if (
|
|
!obj || typeof obj !== "object" ||
|
|
!("url" in obj) || typeof obj.url !== "string"
|
|
) return false;
|
|
const o = obj as Record<string, unknown>;
|
|
if ("comment" in o && typeof o.comment !== "string") return false;
|
|
if (
|
|
typeof o.comment === "string" &&
|
|
(o.comment as string).length > VALIDATION.DUMP_COMMENT_MAX
|
|
) return false;
|
|
if ("title" in o && typeof o.title !== "string") return false;
|
|
if (
|
|
typeof o.title === "string" &&
|
|
(o.title as string).length > VALIDATION.DUMP_TITLE_MAX
|
|
) return false;
|
|
if ("isPrivate" in o && typeof o.isPrivate !== "boolean") return false;
|
|
if ("categoryIds" in o && !isStringArray(o.categoryIds)) return false;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Checks whether `obj` is an {@link UpdateDumpRequest} whose title and comment
|
|
* fit the `VALIDATION` limits. Unlike on creation, a title that is given must
|
|
* not be blank.
|
|
*/
|
|
export function isUpdateDumpRequest(obj: unknown): obj is UpdateDumpRequest {
|
|
if (!obj || typeof obj !== "object") return false;
|
|
const o = obj as Record<string, unknown>;
|
|
if ("url" in o && typeof o.url !== "string") return false;
|
|
// Its format is changeSlug's check, which says what is wrong.
|
|
if ("slug" in o && typeof o.slug !== "string") return false;
|
|
if ("title" in o) {
|
|
if (typeof o.title !== "string") return false;
|
|
const trimmed = (o.title as string).trim();
|
|
if (!trimmed || trimmed.length > VALIDATION.DUMP_TITLE_MAX) return false;
|
|
}
|
|
if (
|
|
"comment" in o && typeof o.comment !== "string" && o.comment !== null
|
|
) return false;
|
|
if (
|
|
typeof o.comment === "string" &&
|
|
(o.comment as string).length > VALIDATION.DUMP_COMMENT_MAX
|
|
) return false;
|
|
if ("isPrivate" in o && typeof o.isPrivate !== "boolean") return false;
|
|
if ("categoryIds" in o && !isStringArray(o.categoryIds)) return false;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Parses a multipart form's `categoryIds`, a JSON array sent as a string. A
|
|
* missing or malformed value counts as no category. The dumps and files
|
|
* routes read the forms that post and replace a file with it.
|
|
*/
|
|
export function parseCategoryIds(value: FormDataEntryValue | null): string[] {
|
|
if (typeof value !== "string" || !value) return [];
|
|
try {
|
|
const parsed: unknown = JSON.parse(value);
|
|
return Array.isArray(parsed)
|
|
? parsed.filter((v): v is string => typeof v === "string")
|
|
: [];
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|