Files
Jyotisha/frontend/src/lib/birth-time-dynamic-choice.ts
T
2026-07-19 04:38:20 +08:00

67 lines
2.5 KiB
TypeScript

import { z } from "zod";
import {
DYNAMIC_QUESTION_LABEL_MAX_LENGTH,
DYNAMIC_QUESTION_PROMPT_MAX_LENGTH,
} from "./birth-time-dynamic-question-limits.ts";
const timeSchema = z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/);
export const publicChoiceKindSchema = z.enum(["primary", "unknown", "unmatched"]);
export type PublicChoiceKind = z.infer<typeof publicChoiceKindSchema>;
export const timeRangeSchema = z.object({
startTime: timeSchema,
endTime: timeSchema,
}).strict().readonly();
export type TimeRange = {
readonly startTime: string;
readonly endTime: string;
};
export const publicDynamicChoiceOptionSchema = z.object({
optionId: z.string().trim().min(1),
label: z.string().trim().min(1).max(DYNAMIC_QUESTION_LABEL_MAX_LENGTH),
kind: publicChoiceKindSchema,
}).strict().readonly();
export type PublicDynamicChoiceQuestion = {
readonly questionId: string;
readonly prompt: string;
readonly options: readonly {
readonly optionId: string;
readonly label: string;
readonly kind: PublicChoiceKind;
}[];
};
function validateOptionSet(
value: { readonly options: readonly { readonly optionId: string; readonly kind: PublicChoiceKind }[] },
context: z.RefinementCtx,
): void {
const primaryCount = value.options.filter((option) => option.kind === "primary").length;
const unknownCount = value.options.filter((option) => option.kind === "unknown").length;
const unmatchedCount = value.options.filter((option) => option.kind === "unmatched").length;
if (primaryCount < 2 || primaryCount > 4) {
context.addIssue({ code: z.ZodIssueCode.custom, path: ["options"], message: "questions require two to four primary options" });
}
if (unknownCount !== 1) {
context.addIssue({ code: z.ZodIssueCode.custom, path: ["options"], message: "questions require one unknown option" });
}
if (unmatchedCount !== 1) {
context.addIssue({ code: z.ZodIssueCode.custom, path: ["options"], message: "questions require one unmatched option" });
}
if (new Set(value.options.map((option) => option.optionId)).size !== value.options.length) {
context.addIssue({ code: z.ZodIssueCode.custom, path: ["options"], message: "question option ids must be unique" });
}
}
export const publicDynamicChoiceQuestionSchema = z.object({
questionId: z.string().trim().min(1),
prompt: z.string().trim().min(1).max(DYNAMIC_QUESTION_PROMPT_MAX_LENGTH),
options: z.array(publicDynamicChoiceOptionSchema).readonly(),
}).strict().superRefine(validateOptionSet).readonly();
export { validateOptionSet };