UNPKG

@overture-stack/lyric

Version:
373 lines (372 loc) 12.5 kB
import { z as zod } from 'zod'; import { isAuditEventValid, isSubmissionActionTypeValid } from './auditUtils.js'; import { parseSQON } from './convertSqonToQuery.js'; import { isValidDateFormat, isValidIdNumber } from './formatUtils.js'; import { VIEW_TYPE } from './types.js'; const auditEventTypeSchema = zod .string() .trim() .min(1) .refine((value) => isAuditEventValid(value), 'invalid Event Type'); const booleanSchema = zod .string() .toLowerCase() .refine((value) => value === 'true' || value === 'false'); const viewSchema = zod.string().toLowerCase().trim().min(1).pipe(VIEW_TYPE); const categoryIdSchema = zod .string() .trim() .min(1) .refine((value) => { const parsed = parseInt(value); return isValidIdNumber(parsed); }, 'invalid category ID'); const endDateSchema = zod .string() .trim() .min(1) .refine((value) => isValidDateFormat(value), 'invalid `endDate` parameter'); const entityNameSchema = zod.string().trim().min(1); const organizationSchema = zod.string().trim().min(1); const pageSizeSchema = zod.string().superRefine((value, ctx) => { const parsed = parseInt(value); if (isNaN(parsed)) { ctx.addIssue({ code: zod.ZodIssueCode.invalid_type, expected: 'number', received: 'nan', }); } if (parsed < 1) { ctx.addIssue({ code: zod.ZodIssueCode.too_small, minimum: 1, inclusive: true, type: 'number', }); } }); const indexIntegerSchema = zod.string().superRefine((value, ctx) => { const parsed = parseInt(value); if (isNaN(parsed)) { ctx.addIssue({ code: zod.ZodIssueCode.invalid_type, expected: 'number', received: 'nan', }); } }); const positiveInteger = zod.string().superRefine((value, ctx) => { const parsed = parseInt(value); if (isNaN(parsed)) { ctx.addIssue({ code: zod.ZodIssueCode.invalid_type, expected: 'number', received: 'nan', }); } if (parsed < 1) { ctx.addIssue({ code: zod.ZodIssueCode.too_small, minimum: 1, inclusive: true, type: 'number', }); } }); const sqonSchema = zod.custom((value) => { try { parseSQON(value); return true; } catch (error) { return false; } }, 'invalid SQON format'); const startDateSchema = zod .string() .trim() .min(1) .refine((value) => isValidDateFormat(value), 'invalid `startDate` parameter'); const submissionActionTypeSchema = zod .string() .trim() .min(1) .refine((value) => isSubmissionActionTypeValid(value), 'invalid Submission Action Type'); const submissionIdSchema = zod .string() .trim() .min(1) .refine((value) => { const parsed = parseInt(value); return isValidIdNumber(parsed); }, 'invalid submission ID'); const migrationIdSchema = zod .string() .trim() .min(1) .refine((value) => { const parsed = parseInt(value); return isValidIdNumber(parsed); }, 'invalid migration ID'); const stringNotEmpty = zod.string().trim().min(1); export const categoryPathParamsSchema = zod.object({ categoryId: categoryIdSchema, }); // Common Category and Organization Path Params export const categoryOrganizationPathParamsSchema = zod.object({ categoryId: categoryIdSchema, organization: organizationSchema, }); // Common Category, Organization, and EntityName Path Params export const categoryOrganizationEntityPathParamsSchema = zod.object({ categoryId: categoryIdSchema, organizationId: organizationSchema, entityName: entityNameSchema, }); const submissionIdPathParamSchema = zod.object({ submissionId: submissionIdSchema, }); const migrationIdPathParamSchema = zod.object({ migrationId: migrationIdSchema, }); const paginationQuerySchema = zod.object({ page: positiveInteger.optional(), pageSize: pageSizeSchema.optional(), }); const auditQuerySchema = zod .object({ entityName: entityNameSchema.optional(), eventType: auditEventTypeSchema.optional(), systemId: stringNotEmpty.optional(), startDate: startDateSchema.optional(), endDate: endDateSchema.optional(), }) .merge(paginationQuerySchema); export const auditByCatAndOrgRequestSchema = { query: auditQuerySchema, pathParams: categoryOrganizationPathParamsSchema, }; // Category Request export const categoryDetailsRequestSchema = { pathParams: categoryPathParamsSchema, }; export const dictionaryRegisterRequestSchema = { body: zod.object({ categoryName: stringNotEmpty, dictionaryName: stringNotEmpty, dictionaryVersion: stringNotEmpty, defaultCentricEntity: entityNameSchema.or(zod.literal('')).optional(), }), query: zod.object({ force: booleanSchema.default('false'), }), }; // Migration Requests export const migrationByIdRequestSchema = { pathParams: migrationIdPathParamSchema, }; export const migrationsByCategoryIdRequestSchema = { pathParams: categoryPathParamsSchema, query: paginationQuerySchema, }; export const migrationDataRequestSchema = { pathParams: migrationIdPathParamSchema, query: zod .object({ entityNames: zod.union([entityNameSchema, entityNameSchema.array()]).optional(), organizations: zod.union([organizationSchema, organizationSchema.array()]).optional(), isInvalid: booleanSchema.default('false'), }) .merge(paginationQuerySchema), }; export const submissionsByCategoryRequestSchema = { query: zod.object({ onlyActive: booleanSchema.default('false'), organization: organizationSchema.optional(), username: stringNotEmpty.optional(), }), pathParams: categoryPathParamsSchema, }; export const submissionByIdRequestSchema = { pathParams: submissionIdPathParamSchema, }; export const submissionDetailsRequestSchema = { query: zod .object({ entityNames: zod.union([entityNameSchema, entityNameSchema.array()]).optional(), actionTypes: zod.union([submissionActionTypeSchema, submissionActionTypeSchema.array()]).optional(), }) .merge(paginationQuerySchema), pathParams: submissionIdPathParamSchema, }; export const submissionActiveByOrganizationRequestSchema = { pathParams: categoryOrganizationPathParamsSchema, }; export const submissionCommitRequestSchema = { pathParams: zod.object({ categoryId: categoryIdSchema, submissionId: submissionIdSchema, }), }; export const submissionDeleteRequestSchema = { pathParams: submissionIdPathParamSchema, query: zod.object({ force: booleanSchema.default('false'), }), }; export const submissionDeleteEntityNameRequestSchema = { query: zod.object({ entityName: entityNameSchema, index: indexIntegerSchema.optional(), }), pathParams: zod.object({ actionType: submissionActionTypeSchema, submissionId: submissionIdSchema, }), }; const uploadSubmissionQueryParams = zod.object({ entityName: entityNameSchema, organization: organizationSchema, }); const submissionUploadFilesQueryParams = zod.object({ organization: organizationSchema, sync: zod.enum(['true', 'false']).optional(), }); export const filenameEntityPair = zod.object({ filename: zod.string(), entity: zod.string(), }); const dataRecordValueSchema = zod.union([ zod.string(), zod.number(), zod.boolean(), zod.array(zod.string()), zod.array(zod.number()), zod.array(zod.boolean()), zod.undefined(), ]); const dataRecordSchema = zod.record(dataRecordValueSchema); export const uploadSubmissionRequestSchema = { pathParams: categoryPathParamsSchema, query: submissionUploadFilesQueryParams, // Multer populates req.body with string-valued form fields from multipart requests. // When fileEntityMap is sent as a JSON-encoded form field, req.body is { fileEntityMap: '...' }. // When no text fields are sent, req.body is an empty null-prototype object {}. // The preprocess step extracts and parses the fileEntityMap field when present, // and coerces all other values (empty object, non-array) to undefined. body: zod.preprocess((value) => { if (Array.isArray(value)) { return value; } if (typeof value !== 'string') { return undefined; } try { const parsed = JSON.parse(value); // The value provided by Swagger will be an array encoded as a string, where and the content // of that array may be either JSON objects, or stringified JSON objects. We will accomodate // an input of either form since the formatting is ambiguous. In particular, the swagger interface // will stringify each element of the array so we require the extra type check inside the map over // the parsed input, but a developer may want to simply build the entire array and then stringify // the entire thing. Both are reasonable. return Array.isArray(parsed) ? parsed.map((item) => (typeof item === 'string' ? JSON.parse(item) : item)) : [parsed]; } catch { return undefined; } }, zod.array(filenameEntityPair).optional()), }; export const uploadSingleEntitySubmissionDataRequestSchema = { body: dataRecordSchema.array(), query: uploadSubmissionQueryParams, pathParams: categoryPathParamsSchema, }; export const dataDeleteBySystemIdRequestSchema = { pathParams: zod.object({ systemId: stringNotEmpty, categoryId: categoryIdSchema, }), }; // TODO: Need type validation for the edit request schema export const editSingleEntityRequestSchema = { body: zod.record(zod.unknown()).array(), query: uploadSubmissionQueryParams, pathParams: categoryPathParamsSchema, }; export const dataGetByCategoryRequestSchema = { query: zod .object({ entityName: zod.union([entityNameSchema, entityNameSchema.array()]).optional(), view: viewSchema.optional(), }) .merge(paginationQuerySchema) .superRefine((data, ctx) => { if (data.view === VIEW_TYPE.Values.compound && data.entityName && data.entityName?.length > 0) { ctx.addIssue({ code: zod.ZodIssueCode.custom, message: 'is incompatible with `compound` view', path: ['entityName'], }); } }), pathParams: categoryPathParamsSchema, }; export const dataGetByOrganizationRequestSchema = { query: zod .object({ entityName: zod.union([entityNameSchema, entityNameSchema.array()]).optional(), view: viewSchema.optional(), }) .merge(paginationQuerySchema) .superRefine((data, ctx) => { if (data.view === VIEW_TYPE.Values.compound && data.entityName && data.entityName?.length > 0) { ctx.addIssue({ code: zod.ZodIssueCode.custom, message: 'is incompatible with `compound` view', path: ['entityName'], }); } }), pathParams: categoryOrganizationPathParamsSchema, }; export const dataGetByQueryRequestSchema = { body: sqonSchema, query: zod .object({ entityName: zod.union([entityNameSchema, entityNameSchema.array()]).optional(), }) .merge(paginationQuerySchema), pathParams: categoryOrganizationPathParamsSchema, }; export const DataGetBySystemIdRequestSchema = { query: zod.object({ view: viewSchema.optional(), }), pathParams: zod.object({ systemId: stringNotEmpty, categoryId: categoryIdSchema, }), }; export const downloadDataFileTemplatesSchema = { query: zod.object({ fileType: zod.enum(['csv', 'tsv']).optional(), }), pathParams: zod.object({ categoryId: categoryIdSchema, }), }; export const validationPathParamsSchema = zod.object({ categoryId: categoryIdSchema, entityName: entityNameSchema, }); const validationQuerySchema = zod.object({ organization: organizationSchema, value: stringNotEmpty, }); export const validationRequestSchema = { query: validationQuerySchema, pathParams: validationPathParamsSchema, };