Skip to content

Commit 3c0a3d2

Browse files
committed
refactor: add type safety to createUploadTemplateCSV dealing with ZodEffect type forms #995
1 parent 5f1f0c5 commit 3c0a3d2

File tree

1 file changed

+27
-16
lines changed

1 file changed

+27
-16
lines changed

apps/web/src/features/upload/utils.ts

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ const ZOD_TYPE_NAMES = [
2020
'ZodDate',
2121
'ZodEnum',
2222
'ZodArray',
23-
'ZodObject'
23+
'ZodObject',
24+
'ZodEffects'
2425
] as const;
2526

2627
const INTERNAL_HEADERS = ['subjectID', 'date'];
@@ -31,7 +32,7 @@ const INTERNAL_HEADERS_SAMPLE_DATA = [MONGOLIAN_VOWEL_SEPARATOR + 'string', MONG
3132

3233
type ZodTypeName = Extract<`${z.ZodFirstPartyTypeKind}`, (typeof ZOD_TYPE_NAMES)[number]>;
3334

34-
type RequiredZodTypeName = Exclude<ZodTypeName, 'ZodOptional'>;
35+
type RequiredZodTypeName = Exclude<ZodTypeName, 'ZodEffects' | 'ZodOptional'>;
3536

3637
type ZodTypeNameResult =
3738
| {
@@ -79,6 +80,14 @@ function isZodArrayDef(def: AnyZodTypeDef): def is z.ZodArrayDef {
7980
return def.typeName === z.ZodFirstPartyTypeKind.ZodArray;
8081
}
8182

83+
function isZodEffectsDef(def: AnyZodTypeDef): def is z.ZodEffectsDef {
84+
return def.typeName === z.ZodFirstPartyTypeKind.ZodEffects;
85+
}
86+
87+
function isZodObjectDef(def: AnyZodTypeDef): def is z.ZodObjectDef {
88+
return def.typeName === z.ZodFirstPartyTypeKind.ZodObject;
89+
}
90+
8291
// TODO - fix extract set and record array functions to handle whitespace and trailing semicolon (present or included)
8392

8493
function extractSetEntry(entry: string) {
@@ -217,7 +226,7 @@ export function interpretZodArray(
217226

218227
export function interpretZodValue(
219228
entry: string,
220-
zType: Exclude<ZodTypeName, 'ZodArray' | 'ZodObject' | 'ZodOptional'>,
229+
zType: Exclude<ZodTypeName, 'ZodArray' | 'ZodEffects' | 'ZodObject' | 'ZodOptional'>,
221230
isOptional: boolean
222231
): UploadOperationResult<FormTypes.FieldValue> {
223232
if (entry === '' && isOptional) {
@@ -245,7 +254,6 @@ export function interpretZodValue(
245254
return { success: true, value: parseNumber(entry) };
246255
}
247256
return { message: `Invalid number type: ${entry}`, success: false };
248-
//TODO if ZodSet has a enum see if those values can be shown in template data if possible
249257
case 'ZodSet':
250258
if (entry.startsWith('SET(')) {
251259
const setData = extractSetEntry(entry);
@@ -327,7 +335,7 @@ function generateSampleData({
327335
multiKeys,
328336
multiValues,
329337
typeName
330-
}: Extract<ZodTypeNameResult, { success: true }>) {
338+
}: Extract<Exclude<ZodTypeNameResult, 'ZodEffects'>, { success: true }>) {
331339
switch (typeName) {
332340
case 'ZodBoolean':
333341
return formatTypeInfo('true/false', isOptional);
@@ -383,7 +391,6 @@ function generateSampleData({
383391
} catch {
384392
throw new Error('Invalid Record Array Error');
385393
}
386-
387394
default:
388395
throw new Error(`Invalid zod schema: unexpected type name '${typeName satisfies never}'`);
389396
}
@@ -393,12 +400,15 @@ export function createUploadTemplateCSV(instrument: AnyUnilingualFormInstrument)
393400
// TODO - type validationSchema as object
394401
const instrumentSchema = instrument.validationSchema as z.AnyZodObject;
395402

403+
const instrumentSchemaDef: unknown = instrument.validationSchema._def;
404+
396405
let shape: { [key: string]: z.ZodTypeAny } = {};
397-
// TODO - include ZodEffect as a typename like our other types
398-
if ((instrumentSchema._def.typeName as string) === 'ZodEffects') {
399-
// @ts-expect-error - TODO - find a type safe way to call this
400-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
401-
shape = instrumentSchema._def.schema._def.shape() as { [key: string]: z.ZodTypeAny };
406+
407+
if (isZodTypeDef(instrumentSchemaDef) && isZodEffectsDef(instrumentSchemaDef)) {
408+
const innerSchema: unknown = instrumentSchemaDef.schema._def;
409+
if (isZodTypeDef(innerSchema) && isZodObjectDef(innerSchema)) {
410+
shape = innerSchema.shape() as { [key: string]: z.ZodTypeAny };
411+
}
402412
} else {
403413
shape = instrumentSchema.shape as { [key: string]: z.ZodTypeAny };
404414
}
@@ -432,13 +442,14 @@ export async function processInstrumentCSV(
432442
let shape: { [key: string]: z.ZodTypeAny } = {};
433443
let instrumentSchemaWithInternal: z.AnyZodObject;
434444

435-
if ((instrumentSchema._def.typeName as string) === 'ZodEffects') {
436-
// @ts-expect-error - TODO - find a type safe way to call this
437-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
438-
instrumentSchemaWithInternal = instrumentSchema._def.schema.extend({
445+
const instrumentSchemaDef: unknown = instrumentSchema._def;
446+
447+
if (isZodTypeDef(instrumentSchemaDef) && isZodEffectsDef(instrumentSchemaDef)) {
448+
//TODO make this type safe without having to cast z.AnyZodObject
449+
instrumentSchemaWithInternal = (instrumentSchemaDef.schema as z.AnyZodObject).extend({
439450
date: z.coerce.date(),
440451
subjectID: z.string()
441-
}) as z.AnyZodObject;
452+
});
442453

443454
shape = instrumentSchemaWithInternal._def.shape() as { [key: string]: z.ZodTypeAny };
444455
} else {

0 commit comments

Comments
 (0)