From 9cebb9728ebb64f4be99f94110e88b5d9d07960c Mon Sep 17 00:00:00 2001 From: Morten Nordseth <43166974+mortennordseth@users.noreply.github.com> Date: Thu, 6 Jun 2024 18:31:57 +0200 Subject: [PATCH] refactor: parse global messages using zod (#301) * refactor: parse global messages using zod * fix: use default false instead of optional * refactor: use optional chaining for function call Co-authored-by: Mikael Brevik --------- Co-authored-by: Mikael Brevik --- src/modules/global-messages/converters.ts | 98 +++++------------------ src/modules/global-messages/types.ts | 38 +++++---- 2 files changed, 44 insertions(+), 92 deletions(-) diff --git a/src/modules/global-messages/converters.ts b/src/modules/global-messages/converters.ts index 6580e40c..34aff106 100644 --- a/src/modules/global-messages/converters.ts +++ b/src/modules/global-messages/converters.ts @@ -1,50 +1,5 @@ -import { isArray } from 'lodash'; -import { GlobalMessageContextEnum, GlobalMessageType } from './types'; -import { MessageMode } from '@atb/components/message-box'; -import { QueryDocumentSnapshot } from 'firebase/firestore'; -import { LocalizedString } from '@atb/translations/commons'; - -export function mapToLocalizedStringArray( - data: any, -): LocalizedString[] | undefined { - if (!data) return; - if (!isArray(data)) return; - - return data - .map((ls: any) => mapToLocalizedString(ls)) - .filter((lv): lv is LocalizedString => !!lv); -} - -function mapToLocalizedString(data: any): LocalizedString | undefined { - if (!data) return; - if (data.lang != 'nob' && data.lang != 'eng' && data.lang != 'nno') return; - - return { - lang: data.lang, - value: data.value, - }; -} - -function mapToContexts(data: any): GlobalMessageContextEnum[] | undefined { - if (!isArray(data)) return; - - return data - .map((context: any) => mapToContext(context)) - .filter(Boolean) as GlobalMessageContextEnum[]; -} - -function mapToContext(data: any): GlobalMessageContextEnum | undefined { - if (!Object.values(GlobalMessageContextEnum).includes(data)) return; - return data as GlobalMessageContextEnum; -} - -function mapToMessageType(type: any) { - if (typeof type !== 'string') return; - - const options = ['info', 'valid', 'warning', 'error']; - if (!options.includes(type)) return; - return type as MessageMode; -} +import { GlobalMessageType, globalMessageTypeSchema } from './types'; +import { QueryDocumentSnapshot, Timestamp } from 'firebase/firestore'; export const globalMessageConverter = { toFirestore(_any: any) { @@ -54,40 +9,29 @@ export const globalMessageConverter = { snapshot: QueryDocumentSnapshot, ): GlobalMessageType | undefined { const data = snapshot.data(); + const potential = { + id: snapshot.id, + active: data.active, + title: data.title, + body: data.body, + type: data.type, + subtle: data.subtle, + context: data.context, + isDismissable: data.isDismissable, + startDate: firestoreTimestampToDate(data.startDate), + endDate: firestoreTimestampToDate(data.endDate), + }; - if (!hasNecessaryGlobalMessageTypeFields(data)) { - return undefined; - } - - const active = data.active; - const body = mapToLocalizedStringArray(data.body); - const title = mapToLocalizedStringArray(data.title); - const context = mapToContexts(data.context); - const type = mapToMessageType(data.type); - const subtle = data.subtle ?? false; - const isDismissable = data.isDismissable ?? false; - const startDate = data.startDate; - const endDate = data.endDate; + const validated = globalMessageTypeSchema.safeParse(potential); - if (!body || !context || !type) return; + if (validated.success) { + return validated.data; + } - return { - id: snapshot.id, - type, - subtle, - active, - context, - body, - title, - isDismissable, - startDate, - endDate, - }; + return undefined; }, }; -function hasNecessaryGlobalMessageTypeFields(data: any) { - return ( - 'active' in data && 'body' in data && 'context' in data && 'type' in data - ); +function firestoreTimestampToDate(timestamp: any) { + return timestamp?.toDate?.(); } diff --git a/src/modules/global-messages/types.ts b/src/modules/global-messages/types.ts index 4d5cf4d2..ed470690 100644 --- a/src/modules/global-messages/types.ts +++ b/src/modules/global-messages/types.ts @@ -1,20 +1,28 @@ -import { MessageMode } from '@atb/components/message-box'; -import { LocalizedString } from '@atb/translations/commons'; -import { Timestamp } from 'firebase/firestore'; +import { languageAndTextSchema } from '@atb/translations/types'; +import { z } from 'zod'; + +const messageModeSchema = z.union([ + z.literal('info'), + z.literal('valid'), + z.literal('warning'), + z.literal('error'), +]); export enum GlobalMessageContextEnum { plannerWebAssistant = 'planner-web-assistant', } -export type GlobalMessageType = { - id: string; - active: boolean; - title?: LocalizedString[]; - body: LocalizedString[]; - type: MessageMode; - subtle?: boolean; - context: GlobalMessageContextEnum[]; - isDismissable?: boolean; - startDate?: Timestamp; - endDate?: Timestamp; -}; +export const globalMessageTypeSchema = z.object({ + id: z.string(), + active: z.boolean(), + title: z.array(languageAndTextSchema).optional(), + body: z.array(languageAndTextSchema), + type: messageModeSchema, + subtle: z.boolean().default(false), + context: z.array(z.nativeEnum(GlobalMessageContextEnum)), + isDismissable: z.boolean().default(false), + startDate: z.date().optional(), + endDate: z.date().optional(), +}); + +export type GlobalMessageType = z.infer;