diff --git a/packages/pl-api/lib/entities/account.ts b/packages/pl-api/lib/entities/account.ts index c9816f3b99..91cd28da9a 100644 --- a/packages/pl-api/lib/entities/account.ts +++ b/packages/pl-api/lib/entities/account.ts @@ -88,7 +88,7 @@ const baseAccountSchema = v.object({ suspended: v.fallback(v.optional(v.boolean()), undefined), limited: v.fallback(v.optional(v.boolean()), undefined), created_at: z.string().datetime().catch(new Date().toUTCString()), - last_status_at: v.fallback(v.nullable(z.string().date()), null), + last_status_at: v.fallback(v.nullable(v.pipe(v.string(), v.isoDate())), null), statuses_count: v.fallback(v.number(), 0), followers_count: v.fallback(v.number(), 0), following_count: v.fallback(v.number(), 0), @@ -108,7 +108,7 @@ const baseAccountSchema = v.object({ hide_follows_count: v.fallback(v.optional(v.boolean()), undefined), accepts_chat_messages: v.fallback(v.nullable(v.boolean()), null), favicon: v.fallback(v.optional(v.string()), undefined), - birthday: z.string().date().optional().catch(undefined), + birthday: v.fallback(v.optional(v.pipe(v.string(), v.isoDate())), undefined), deactivated: v.fallback(v.optional(v.boolean()), undefined), location: v.fallback(v.optional(v.string()), undefined), @@ -126,7 +126,8 @@ const baseAccountSchema = v.object({ }), }); -const accountWithMovedAccountSchema = baseAccountSchema.extend({ +const accountWithMovedAccountSchema = v.object({ + ...baseAccountSchema.entries, moved: v.fallback(v.nullable(z.lazy((): typeof baseAccountSchema => accountWithMovedAccountSchema as any)), null), }); @@ -141,7 +142,8 @@ type Account = v.InferOutput & WithMoved; const accountSchema: z.ZodType = untypedAccountSchema as any; -const untypedCredentialAccountSchema = z.preprocess(preprocessAccount, accountWithMovedAccountSchema.extend({ +const untypedCredentialAccountSchema = z.preprocess(preprocessAccount, v.object({ + ...accountWithMovedAccountSchema.entries, source: v.fallback(v.nullable(v.object({ note: v.fallback(v.string(), ''), fields: filteredArray(fieldSchema), @@ -173,7 +175,8 @@ type CredentialAccount = v.InferOutput & const credentialAccountSchema: z.ZodType = untypedCredentialAccountSchema as any; -const untypedMutedAccountSchema = z.preprocess(preprocessAccount, accountWithMovedAccountSchema.extend({ +const untypedMutedAccountSchema = z.preprocess(preprocessAccount, v.object({ + ...accountWithMovedAccountSchema.entries, mute_expires_at: v.fallback(v.nullable(dateSchema), null), })); diff --git a/packages/pl-api/lib/entities/admin/account.ts b/packages/pl-api/lib/entities/admin/account.ts index 74fc5e1cc6..2df73feb27 100644 --- a/packages/pl-api/lib/entities/admin/account.ts +++ b/packages/pl-api/lib/entities/admin/account.ts @@ -42,7 +42,7 @@ const adminAccountSchema = z.preprocess((account: any) => { domain: v.fallback(v.nullable(v.string()), null), created_at: dateSchema, email: v.fallback(v.nullable(v.string()), null), - ip: v.fallback(v.nullable(z.string().ip()), null), + ip: v.fallback(v.nullable(v.pipe(v.string(), v.ip())), null), ips: filteredArray(adminIpSchema), locale: v.fallback(v.nullable(v.string()), null), invite_request: v.fallback(v.nullable(v.string()), null), diff --git a/packages/pl-api/lib/entities/admin/announcement.ts b/packages/pl-api/lib/entities/admin/announcement.ts index 3a670a7f40..0f44336b26 100644 --- a/packages/pl-api/lib/entities/admin/announcement.ts +++ b/packages/pl-api/lib/entities/admin/announcement.ts @@ -7,7 +7,8 @@ import { announcementSchema } from '../announcement'; const adminAnnouncementSchema = z.preprocess((announcement: any) => ({ ...announcement, ...pick(announcement.pleroma, 'raw_content'), -}), announcementSchema.extend({ +}), v.object({ + ...announcementSchema.entries, raw_content: v.fallback(v.string(), ''), })); diff --git a/packages/pl-api/lib/entities/admin/tag.ts b/packages/pl-api/lib/entities/admin/tag.ts index 983a645386..aaa2818f6e 100644 --- a/packages/pl-api/lib/entities/admin/tag.ts +++ b/packages/pl-api/lib/entities/admin/tag.ts @@ -3,7 +3,8 @@ import * as v from 'valibot'; import { tagSchema } from '../tag'; /** @see {@link https://docs.joinmastodon.org/entities/Tag/#admin} */ -const adminTagSchema = tagSchema.extend({ +const adminTagSchema = v.object({ + ...tagSchema.entries, id: v.string(), trendable: v.boolean(), usable: v.boolean(), diff --git a/packages/pl-api/lib/entities/directory/statistics-period.ts b/packages/pl-api/lib/entities/directory/statistics-period.ts index e482643294..ea37a321d0 100644 --- a/packages/pl-api/lib/entities/directory/statistics-period.ts +++ b/packages/pl-api/lib/entities/directory/statistics-period.ts @@ -1,7 +1,7 @@ import * as v from 'valibot'; const directoryStatisticsPeriodSchema = v.object({ - period: z.string().date(), + period: v.pipe(v.string(), v.isoDate()), server_count: v.fallback(v.nullable(v.pipe(v.unknown(), v.transform(Number))), null), user_count: v.fallback(v.nullable(v.pipe(v.unknown(), v.transform(Number))), null), active_user_count: v.fallback(v.nullable(v.pipe(v.unknown(), v.transform(Number))), null), diff --git a/packages/pl-api/lib/entities/media-attachment.ts b/packages/pl-api/lib/entities/media-attachment.ts index 341378f8a0..e66cf1fd6c 100644 --- a/packages/pl-api/lib/entities/media-attachment.ts +++ b/packages/pl-api/lib/entities/media-attachment.ts @@ -51,7 +51,8 @@ const videoAttachmentSchema = v.object({ type: v.literal('video'), meta: v.fallback(v.object({ duration: v.fallback(v.optional(v.number()), undefined), - original: v.fallback(v.optional(imageMetaSchema.extend({ + original: v.fallback(v.optional(v.object({ + ...imageMetaSchema.entries, frame_rate: v.fallback(v.nullable(v.pipe(v.string(), v.regex(/\d+\/\d+$/))), null), duration: v.fallback(v.nullable(z.number().nonnegative()), null), })), undefined), diff --git a/packages/pl-api/lib/entities/notification.ts b/packages/pl-api/lib/entities/notification.ts index 128344dcf1..2a687dc7a4 100644 --- a/packages/pl-api/lib/entities/notification.ts +++ b/packages/pl-api/lib/entities/notification.ts @@ -20,54 +20,64 @@ const baseNotificationSchema = v.object({ is_seen: v.fallback(v.optional(v.boolean()), undefined), }); -const accountNotificationSchema = baseNotificationSchema.extend({ +const accountNotificationSchema = v.object({ + ...baseNotificationSchema.entries, type: v.picklist(['follow', 'follow_request', 'admin.sign_up', 'bite']), }); -const mentionNotificationSchema = baseNotificationSchema.extend({ +const mentionNotificationSchema = v.object({ + ...baseNotificationSchema.entries, type: v.literal('mention'), subtype: v.fallback(v.nullable(v.picklist(['reply'])), null), status: statusSchema, }); -const statusNotificationSchema = baseNotificationSchema.extend({ +const statusNotificationSchema = v.object({ + ...baseNotificationSchema.entries, type: v.picklist(['status', 'reblog', 'favourite', 'poll', 'update', 'event_reminder']), status: statusSchema, }); -const reportNotificationSchema = baseNotificationSchema.extend({ +const reportNotificationSchema = v.object({ + ...baseNotificationSchema.entries, type: v.literal('admin.report'), report: reportSchema, }); -const severedRelationshipNotificationSchema = baseNotificationSchema.extend({ +const severedRelationshipNotificationSchema = v.object({ + ...baseNotificationSchema.entries, type: v.literal('severed_relationships'), relationship_severance_event: relationshipSeveranceEventSchema, }); -const moderationWarningNotificationSchema = baseNotificationSchema.extend({ +const moderationWarningNotificationSchema = v.object({ + ...baseNotificationSchema.entries, type: v.literal('moderation_warning'), moderation_warning: accountWarningSchema, }); -const moveNotificationSchema = baseNotificationSchema.extend({ +const moveNotificationSchema = v.object({ + ...baseNotificationSchema.entries, type: v.literal('move'), target: accountSchema, }); -const emojiReactionNotificationSchema = baseNotificationSchema.extend({ +const emojiReactionNotificationSchema = v.object({ + ...baseNotificationSchema.entries, type: v.literal('emoji_reaction'), emoji: v.string(), emoji_url: v.fallback(v.nullable(v.string()), null), status: statusSchema, }); -const chatMentionNotificationSchema = baseNotificationSchema.extend({ +const chatMentionNotificationSchema = v.object({ + ...baseNotificationSchema.entries, type: v.literal('chat_mention'), chat_message: chatMessageSchema, }); -const eventParticipationRequestNotificationSchema = baseNotificationSchema.extend({ +const eventParticipationRequestNotificationSchema = v.object({ + ...baseNotificationSchema.entries, type: v.picklist(['participation_accepted', 'participation_request']), status: statusSchema, participation_message: v.fallback(v.nullable(v.string()), null), @@ -81,7 +91,7 @@ const notificationSchema: z.ZodType = z.preprocess((notification: type: notification.type === 'pleroma:report' ? 'admin.report' : notification.type?.replace(/^pleroma:/, ''), -}), z.discriminatedUnion('type', [ +}), v.variant('type', [ accountNotificationSchema, mentionNotificationSchema, statusNotificationSchema, diff --git a/packages/pl-api/lib/entities/poll.ts b/packages/pl-api/lib/entities/poll.ts index d95c7802c4..3a76c1ef5a 100644 --- a/packages/pl-api/lib/entities/poll.ts +++ b/packages/pl-api/lib/entities/poll.ts @@ -7,7 +7,7 @@ const pollOptionSchema = v.object({ title: v.fallback(v.string(), ''), votes_count: v.fallback(v.number(), 0), - title_map: v.record(v.string(), v.string()).nullable().catch(null), + title_map: v.fallback(v.nullable(v.record(v.string(), v.string())), null), }); /** @see {@link https://docs.joinmastodon.org/entities/Poll/} */ diff --git a/packages/pl-api/lib/entities/role.ts b/packages/pl-api/lib/entities/role.ts index 173c6ff89a..1b1c2eab93 100644 --- a/packages/pl-api/lib/entities/role.ts +++ b/packages/pl-api/lib/entities/role.ts @@ -1,11 +1,11 @@ import * as v from 'valibot'; -const hexSchema = z.string().regex(/^#[a-f0-9]{6}$/i); +const hexSchema = v.pipe(v.string(), v.regex(/^#[a-f0-9]{6}$/i)); const roleSchema = v.object({ id: v.fallback(v.string(), ''), name: v.fallback(v.string(), ''), - color: hexSchema.catch(''), + color: v.fallback(hexSchema, ''), permissions: v.fallback(v.string(), ''), highlighted: v.fallback(v.boolean(), true), }); diff --git a/packages/pl-api/lib/entities/status-source.ts b/packages/pl-api/lib/entities/status-source.ts index 01516a2002..7b521cfa9c 100644 --- a/packages/pl-api/lib/entities/status-source.ts +++ b/packages/pl-api/lib/entities/status-source.ts @@ -8,11 +8,11 @@ const statusSourceSchema = v.object({ text: v.fallback(v.string(), ''), spoiler_text: v.fallback(v.string(), ''), - content_type: z.string().catch('text/plain'), + content_type: v.fallback(v.string(), 'text/plain'), location: v.fallback(v.nullable(locationSchema), null), - text_map: v.record(v.string(), v.string()).nullable().catch(null), - spoiler_text_map: v.record(v.string(), v.string()).nullable().catch(null), + text_map: v.fallback(v.nullable(v.record(v.string(), v.string())), null), + spoiler_text_map: v.fallback(v.nullable(v.record(v.string(), v.string())), null), }); type StatusSource = v.InferOutput; diff --git a/packages/pl-api/lib/entities/status.ts b/packages/pl-api/lib/entities/status.ts index a013c44be9..c867c45d0b 100644 --- a/packages/pl-api/lib/entities/status.ts +++ b/packages/pl-api/lib/entities/status.ts @@ -90,7 +90,7 @@ const baseStatusSchema = v.object({ bookmark_folder: v.fallback(v.nullable(v.string()), null), event: v.fallback(v.nullable(statusEventSchema), null), - translation: translationSchema.nullable().or(v.literal(false)).catch(null), + translation: v.fallback(v.union([v.nullable(translationSchema), v.literal(false)]), null), content_map: v.fallback(v.nullable(v.record(v.string(), v.string())), null), text_map: v.fallback(v.nullable(v.record(v.string(), v.string())), null), @@ -134,13 +134,15 @@ const preprocess = (status: any) => { return status; }; -const statusSchema: z.ZodType = z.preprocess(preprocess, baseStatusSchema.extend({ +const statusSchema: z.ZodType = z.preprocess(preprocess, v.object({ + ...baseStatusSchema.entries, reblog: v.fallback(v.nullable(z.lazy(() => statusSchema)), null), quote: v.fallback(v.nullable(z.lazy(() => statusSchema)), null), })) as any; -const statusWithoutAccountSchema = z.preprocess(preprocess, baseStatusSchema.omit({ account: true }).extend({ +const statusWithoutAccountSchema = z.preprocess(preprocess, v.object({ + ...(v.omit(baseStatusSchema, ['account']).entries), account: v.fallback(v.nullable(accountSchema), null), reblog: v.fallback(v.nullable(z.lazy(() => statusSchema)), null),