From ea3addf38886c192999cfdd3e8514d2137aa5143 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Mon, 14 Oct 2024 20:54:44 +0200 Subject: [PATCH] pl-api: More blind search and replace before actual testing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- packages/pl-api/lib/client.ts | 6 +- packages/pl-api/lib/entities/account.ts | 36 +++--- packages/pl-api/lib/entities/admin/account.ts | 4 +- packages/pl-api/lib/entities/admin/cohort.ts | 6 +- .../pl-api/lib/entities/admin/domain-block.ts | 2 +- .../pl-api/lib/entities/admin/ip-block.ts | 2 +- .../entities/admin/moderation-log-entry.ts | 2 +- .../lib/entities/admin/pleroma-config.ts | 4 +- packages/pl-api/lib/entities/admin/report.ts | 4 +- packages/pl-api/lib/entities/announcement.ts | 6 +- packages/pl-api/lib/entities/chat.ts | 2 +- .../pl-api/lib/entities/directory/category.ts | 2 +- .../pl-api/lib/entities/directory/language.ts | 2 +- .../pl-api/lib/entities/directory/server.ts | 2 +- .../entities/directory/statistics-period.ts | 6 +- packages/pl-api/lib/entities/domain-block.ts | 2 +- .../pl-api/lib/entities/emoji-reaction.ts | 8 +- packages/pl-api/lib/entities/featured-tag.ts | 4 +- packages/pl-api/lib/entities/filter-result.ts | 4 +- packages/pl-api/lib/entities/filter.ts | 6 +- packages/pl-api/lib/entities/group.ts | 4 +- packages/pl-api/lib/entities/instance.ts | 110 +++++++++--------- .../pl-api/lib/entities/interaction-policy.ts | 6 +- .../lib/entities/interaction-request.ts | 6 +- packages/pl-api/lib/entities/location.ts | 8 +- packages/pl-api/lib/entities/marker.ts | 4 +- .../pl-api/lib/entities/media-attachment.ts | 74 ++++++------ packages/pl-api/lib/entities/mention.ts | 2 +- .../lib/entities/notification-policy.ts | 4 +- .../lib/entities/notification-request.ts | 2 +- packages/pl-api/lib/entities/notification.ts | 22 ++-- packages/pl-api/lib/entities/oauth-token.ts | 2 +- packages/pl-api/lib/entities/poll.ts | 6 +- packages/pl-api/lib/entities/preview-card.ts | 10 +- .../entities/relationship-severance-event.ts | 4 +- packages/pl-api/lib/entities/report.ts | 6 +- .../pl-api/lib/entities/scheduled-status.ts | 8 +- packages/pl-api/lib/entities/status-source.ts | 4 +- packages/pl-api/lib/entities/status.ts | 50 ++++---- .../pl-api/lib/entities/streaming-event.ts | 26 ++--- packages/pl-api/lib/entities/tag.ts | 4 +- packages/pl-api/lib/entities/token.ts | 6 +- packages/pl-api/lib/entities/translation.ts | 2 +- packages/pl-api/lib/entities/trends-link.ts | 6 +- packages/pl-api/lib/entities/utils.ts | 14 ++- .../lib/entities/web-push-subscription.ts | 2 +- packages/pl-fe/src/schemas/utils.ts | 2 +- 47 files changed, 253 insertions(+), 251 deletions(-) diff --git a/packages/pl-api/lib/client.ts b/packages/pl-api/lib/client.ts index 13cc6dd53..9e423ed81 100644 --- a/packages/pl-api/lib/client.ts +++ b/packages/pl-api/lib/client.ts @@ -380,7 +380,7 @@ class PlApiClient { return v.parse(v.intersect([v.object({ type: v.string(), - }), z.record(v.any())]), response.json); + }), v.record(v.string(), v.any())]), response.json); }, mfaChallenge: async (params: MfaChallengeParams) => { @@ -2773,7 +2773,7 @@ class PlApiClient { const response = await this.request('/api/v1/instance/translation_languages'); - return v.parse(z.record(v.array(v.string())), response.json); + return v.parse(v.record(v.string(), v.array(v.string())), response.json); }, /** @@ -2806,7 +2806,7 @@ class PlApiClient { getFrontendConfigurations: async () => { const response = await this.request('/api/pleroma/frontend_configurations'); - return v.parse(z.record(z.record(v.any())).catch({}), response.json); + return v.parse(v.fallback(v.record(v.string(), v.record(v.string(), v.any())), {}), response.json); }, }; diff --git a/packages/pl-api/lib/entities/account.ts b/packages/pl-api/lib/entities/account.ts index ee9a970e2..5af5a0e2d 100644 --- a/packages/pl-api/lib/entities/account.ts +++ b/packages/pl-api/lib/entities/account.ts @@ -64,31 +64,31 @@ const preprocessAccount = (account: any) => { const fieldSchema = v.object({ name: v.string(), value: v.string(), - verified_at: z.string().datetime({ offset: true }).nullable().catch(null), + verified_at: v.fallback(v.nullable(z.string().datetime({ offset: true })), null), }); const baseAccountSchema = v.object({ id: v.string(), username: v.fallback(v.string(), ''), acct: v.fallback(v.string(), ''), - url: z.string().url(), + url: v.pipe(v.string(), v.url()), display_name: v.fallback(v.string(), ''), note: v.fallback(v.string(), ''), avatar: v.fallback(v.string(), ''), - avatar_static: z.string().url().catch(''), - header: z.string().url().catch(''), - header_static: z.string().url().catch(''), + avatar_static: v.fallback(v.pipe(v.string(), v.url()), ''), + header: v.fallback(v.pipe(v.string(), v.url()), ''), + header_static: v.fallback(v.pipe(v.string(), v.url()), ''), locked: v.fallback(v.boolean(), false), fields: filteredArray(fieldSchema), emojis: filteredArray(customEmojiSchema), bot: v.fallback(v.boolean(), false), group: v.fallback(v.boolean(), false), discoverable: v.fallback(v.boolean(), false), - noindex: v.fallback(v.optional(v.nullable()), null), + noindex: v.fallback(v.nullable(v.boolean()), null), 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: z.string().date().nullable().catch(null), + last_status_at: v.fallback(v.nullable(z.string().date()), null), statuses_count: v.fallback(v.number(), 0), followers_count: v.fallback(v.number(), 0), following_count: v.fallback(v.number(), 0), @@ -97,7 +97,7 @@ const baseAccountSchema = v.object({ fqn: v.fallback(v.nullable(v.string()), null), ap_id: v.fallback(v.nullable(v.string()), null), background_image: v.fallback(v.nullable(v.string()), null), - relationship: relationshipSchema.optional().catch(undefined), + relationship: v.fallback(v.optional(relationshipSchema), undefined), is_moderator: v.fallback(v.optional(v.boolean()), undefined), is_admin: v.fallback(v.optional(v.boolean()), undefined), is_suggested: v.fallback(v.optional(v.boolean()), undefined), @@ -106,7 +106,7 @@ const baseAccountSchema = v.object({ hide_follows: v.fallback(v.optional(v.boolean()), undefined), hide_followers_count: v.fallback(v.optional(v.boolean()), undefined), hide_follows_count: v.fallback(v.optional(v.boolean()), undefined), - accepts_chat_messages: v.fallback(v.optional(v.nullable()), null), + 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), deactivated: v.fallback(v.optional(v.boolean()), undefined), @@ -121,13 +121,13 @@ const baseAccountSchema = v.object({ verified: v.fallback(v.optional(v.boolean()), undefined), __meta: coerceObject({ - pleroma: z.any().optional().catch(undefined), - source: z.any().optional().catch(undefined), + pleroma: v.fallback(v.any(), undefined), + source: v.fallback(v.any(), undefined), }), }); const accountWithMovedAccountSchema = baseAccountSchema.extend({ - moved: z.lazy((): typeof baseAccountSchema => accountWithMovedAccountSchema as any).nullable().catch(null), + moved: v.fallback(v.nullable(z.lazy((): typeof baseAccountSchema => accountWithMovedAccountSchema as any)), null), }); /** @see {@link https://docs.joinmastodon.org/entities/Account/} */ @@ -145,7 +145,7 @@ const untypedCredentialAccountSchema = z.preprocess(preprocessAccount, accountWi source: v.object({ note: v.fallback(v.string(), ''), fields: filteredArray(fieldSchema), - privacy: z.enum(['public', 'unlisted', 'private', 'direct']), + privacy: v.picklist(['public', 'unlisted', 'private', 'direct']), sensitive: v.fallback(v.boolean(), false), language: v.fallback(v.nullable(v.string()), null), follow_requests_count: z.number().int().nonnegative().catch(0), @@ -158,15 +158,15 @@ const untypedCredentialAccountSchema = z.preprocess(preprocessAccount, accountWi }).nullable().catch(null), role: v.fallback(v.nullable(roleSchema), null), - settings_store: z.record(z.any()).optional().catch(undefined), + settings_store: v.record(v.string(), v.any()).optional().catch(undefined), chat_token: v.fallback(v.optional(v.string()), undefined), allow_following_move: v.fallback(v.optional(v.boolean()), undefined), - unread_conversation_count: z.number().optional().catch(undefined), - unread_notifications_count: z.number().optional().catch(undefined), - notification_settings: v.object({ + unread_conversation_count: v.fallback(v.optional(v.number()), undefined), + unread_notifications_count: v.fallback(v.optional(v.number()), undefined), + notification_settings: v.fallback(v.optional(v.object({ block_from_strangers: v.fallback(v.boolean(), false), hide_notification_contents: v.fallback(v.boolean(), false), - }).optional().catch(undefined), + })), undefined), })); type CredentialAccount = v.InferOutput & WithMoved; diff --git a/packages/pl-api/lib/entities/admin/account.ts b/packages/pl-api/lib/entities/admin/account.ts index e142ccfe3..8b829ae86 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: z.string().ip().nullable().catch(null), + ip: v.fallback(v.nullable(z.string().ip()), null), ips: filteredArray(adminIpSchema), locale: v.fallback(v.nullable(v.string()), null), invite_request: v.fallback(v.nullable(v.string()), null), @@ -58,7 +58,7 @@ const adminAccountSchema = z.preprocess((account: any) => { actor_type: v.fallback(v.nullable(v.string()), null), display_name: v.fallback(v.nullable(v.string()), null), - suggested: v.fallback(v.optional(v.nullable()), null), + suggested: v.fallback(v.nullable(v.boolean()), null), })); type AdminAccount = v.InferOutput; diff --git a/packages/pl-api/lib/entities/admin/cohort.ts b/packages/pl-api/lib/entities/admin/cohort.ts index 72ae379d6..af4cdcdfd 100644 --- a/packages/pl-api/lib/entities/admin/cohort.ts +++ b/packages/pl-api/lib/entities/admin/cohort.ts @@ -3,11 +3,11 @@ import * as v from 'valibot'; /** @see {@link https://docs.joinmastodon.org/entities/Admin_Cohort/} */ const adminCohortSchema = v.object({ period: z.string().datetime({ offset: true }), - frequency: z.enum(['day', 'month']), + frequency: v.picklist(['day', 'month']), data: z.array(v.object({ date: z.string().datetime({ offset: true }), - rate: z.number(), - value: z.number().int(), + rate: v.number(), + value: v.pipe(v.number(), v.integer()), })), }); diff --git a/packages/pl-api/lib/entities/admin/domain-block.ts b/packages/pl-api/lib/entities/admin/domain-block.ts index 9d87e63a6..d87f18338 100644 --- a/packages/pl-api/lib/entities/admin/domain-block.ts +++ b/packages/pl-api/lib/entities/admin/domain-block.ts @@ -8,7 +8,7 @@ const adminDomainBlockSchema = v.object({ domain: v.string(), digest: v.string(), created_at: dateSchema, - severity: z.enum(['silence', 'suspend', 'noop']), + severity: v.picklist(['silence', 'suspend', 'noop']), reject_media: v.boolean(), reject_reports: v.boolean(), private_comment: v.fallback(v.nullable(v.string()), null), diff --git a/packages/pl-api/lib/entities/admin/ip-block.ts b/packages/pl-api/lib/entities/admin/ip-block.ts index 580627a08..bf470a426 100644 --- a/packages/pl-api/lib/entities/admin/ip-block.ts +++ b/packages/pl-api/lib/entities/admin/ip-block.ts @@ -6,7 +6,7 @@ import { dateSchema } from '../utils'; const adminIpBlockSchema = v.object({ id: v.string(), ip: z.string().ip(), - severity: z.enum(['sign_up_requires_approval', 'sign_up_block', 'no_access']), + severity: v.picklist(['sign_up_requires_approval', 'sign_up_block', 'no_access']), comment: v.fallback(v.string(), ''), created_at: dateSchema, expires_at: z.string().datetime({ offset: true }), diff --git a/packages/pl-api/lib/entities/admin/moderation-log-entry.ts b/packages/pl-api/lib/entities/admin/moderation-log-entry.ts index b901128a1..86ce01075 100644 --- a/packages/pl-api/lib/entities/admin/moderation-log-entry.ts +++ b/packages/pl-api/lib/entities/admin/moderation-log-entry.ts @@ -3,7 +3,7 @@ import * as v from 'valibot'; /** @see {@link https://docs.pleroma.social/backend/development/API/admin_api/#get-apiv1pleromaadminmoderation_log} */ const adminModerationLogEntrySchema = v.object({ id: z.coerce.string(), - data: z.record(v.string(), z.any()).catch({}), + data: v.fallback(v.record(v.string(), v.any()), {}), time: v.fallback(v.number(), 0), message: v.fallback(v.string(), ''), }); diff --git a/packages/pl-api/lib/entities/admin/pleroma-config.ts b/packages/pl-api/lib/entities/admin/pleroma-config.ts index 8fb09f5a3..aee37727a 100644 --- a/packages/pl-api/lib/entities/admin/pleroma-config.ts +++ b/packages/pl-api/lib/entities/admin/pleroma-config.ts @@ -1,8 +1,8 @@ import * as v from 'valibot'; const pleromaConfigSchema = v.object({ - configs: z.array(v.object({ - value: z.any(), + configs: v.array(v.object({ + value: v.any(), group: v.string(), key: v.string(), })), diff --git a/packages/pl-api/lib/entities/admin/report.ts b/packages/pl-api/lib/entities/admin/report.ts index 7b64b0f67..4ae7c4f3c 100644 --- a/packages/pl-api/lib/entities/admin/report.ts +++ b/packages/pl-api/lib/entities/admin/report.ts @@ -31,8 +31,8 @@ const adminReportSchema = z.preprocess((report: any) => { category: v.fallback(v.optional(v.string()), undefined), comment: v.fallback(v.optional(v.string()), undefined), forwarded: v.fallback(v.optional(v.boolean()), undefined), - created_at: dateSchema.optional().catch(undefined), - updated_at: dateSchema.optional().catch(undefined), + created_at: v.fallback(v.optional(dateSchema), undefined), + updated_at: v.fallback(v.optional(dateSchema), undefined), account: adminAccountSchema, target_account: adminAccountSchema, assigned_account: v.fallback(v.nullable(adminAccountSchema), null), diff --git a/packages/pl-api/lib/entities/announcement.ts b/packages/pl-api/lib/entities/announcement.ts index f161e97d0..0be54d135 100644 --- a/packages/pl-api/lib/entities/announcement.ts +++ b/packages/pl-api/lib/entities/announcement.ts @@ -10,8 +10,8 @@ import { dateSchema, filteredArray } from './utils'; const announcementSchema = v.object({ id: v.string(), content: v.fallback(v.string(), ''), - starts_at: z.string().datetime().nullable().catch(null), - ends_at: z.string().datetime().nullable().catch(null), + starts_at: v.fallback(v.nullable(z.string().datetime()), null), + ends_at: v.fallback(v.nullable(z.string().datetime()), null), all_day: v.fallback(v.boolean(), false), read: v.fallback(v.boolean(), false), published_at: dateSchema, @@ -20,7 +20,7 @@ const announcementSchema = v.object({ (statuses: any) => Array.isArray(statuses) ? Object.fromEntries(statuses.map((status: any) => [status.url, status.account?.acct]) || []) : statuses, - z.record(v.string(), v.string()), + v.record(v.string(), v.string(), v.string()), ), mentions: filteredArray(mentionSchema), tags: filteredArray(tagSchema), diff --git a/packages/pl-api/lib/entities/chat.ts b/packages/pl-api/lib/entities/chat.ts index 2d4762e14..cddad9af7 100644 --- a/packages/pl-api/lib/entities/chat.ts +++ b/packages/pl-api/lib/entities/chat.ts @@ -8,7 +8,7 @@ import { dateSchema } from './utils'; const chatSchema = v.object({ id: v.string(), account: accountSchema, - unread: z.number().int(), + unread: v.pipe(v.number(), v.integer()), last_message: v.fallback(v.nullable(chatMessageSchema), null), created_at: dateSchema, }); diff --git a/packages/pl-api/lib/entities/directory/category.ts b/packages/pl-api/lib/entities/directory/category.ts index 19b35cdde..605d8ab0e 100644 --- a/packages/pl-api/lib/entities/directory/category.ts +++ b/packages/pl-api/lib/entities/directory/category.ts @@ -2,7 +2,7 @@ import * as v from 'valibot'; const directoryCategorySchema = v.object({ category: v.string(), - servers_count: z.coerce.number().nullable().catch(null), + servers_count: v.fallback(v.nullable(z.coerce.number()), null), }); type DirectoryCategory = v.InferOutput; diff --git a/packages/pl-api/lib/entities/directory/language.ts b/packages/pl-api/lib/entities/directory/language.ts index e97b397d8..2c0c330a6 100644 --- a/packages/pl-api/lib/entities/directory/language.ts +++ b/packages/pl-api/lib/entities/directory/language.ts @@ -3,7 +3,7 @@ import * as v from 'valibot'; const directoryLanguageSchema = v.object({ locale: v.string(), language: v.string(), - servers_count: z.coerce.number().nullable().catch(null), + servers_count: v.fallback(v.nullable(z.coerce.number()), null), }); type DirectoryLanguage = v.InferOutput; diff --git a/packages/pl-api/lib/entities/directory/server.ts b/packages/pl-api/lib/entities/directory/server.ts index 231cbff3e..3c25e44bb 100644 --- a/packages/pl-api/lib/entities/directory/server.ts +++ b/packages/pl-api/lib/entities/directory/server.ts @@ -7,7 +7,7 @@ const directoryServerSchema = v.object({ languages: z.array(v.string()), region: v.string(), categories: z.array(v.string()), - proxied_thumbnail: z.string().url().nullable().catch(null), + proxied_thumbnail: v.fallback(v.nullable(z.string().url()), null), blurhash: v.fallback(v.nullable(v.string()), null), total_users: z.coerce.number(), last_week_users: z.coerce.number(), diff --git a/packages/pl-api/lib/entities/directory/statistics-period.ts b/packages/pl-api/lib/entities/directory/statistics-period.ts index ab3086cab..f5c909c16 100644 --- a/packages/pl-api/lib/entities/directory/statistics-period.ts +++ b/packages/pl-api/lib/entities/directory/statistics-period.ts @@ -2,9 +2,9 @@ import * as v from 'valibot'; const directoryStatisticsPeriodSchema = v.object({ period: z.string().date(), - server_count: z.coerce.number().nullable().catch(null), - user_count: z.coerce.number().nullable().catch(null), - active_user_count: z.coerce.number().nullable().catch(null), + server_count: v.fallback(v.nullable(z.coerce.number()), null), + user_count: v.fallback(v.nullable(z.coerce.number()), null), + active_user_count: v.fallback(v.nullable(z.coerce.number()), null), }); type DirectoryStatisticsPeriod = v.InferOutput; diff --git a/packages/pl-api/lib/entities/domain-block.ts b/packages/pl-api/lib/entities/domain-block.ts index 97063a7ed..e5bf6e0da 100644 --- a/packages/pl-api/lib/entities/domain-block.ts +++ b/packages/pl-api/lib/entities/domain-block.ts @@ -4,7 +4,7 @@ import * as v from 'valibot'; const domainBlockSchema = v.object({ domain: v.string(), digest: v.string(), - severity: z.enum(['silence', 'suspend']), + severity: v.picklist(['silence', 'suspend']), comment: v.fallback(v.optional(v.string()), undefined), }); diff --git a/packages/pl-api/lib/entities/emoji-reaction.ts b/packages/pl-api/lib/entities/emoji-reaction.ts index 917cdb0d2..8b66d12f9 100644 --- a/packages/pl-api/lib/entities/emoji-reaction.ts +++ b/packages/pl-api/lib/entities/emoji-reaction.ts @@ -7,16 +7,16 @@ const baseEmojiReactionSchema = v.object({ count: v.fallback(v.nullable(v.number()), null), me: v.fallback(v.boolean(), false), name: emojiSchema, - url: z.literal(undefined).catch(undefined), - static_url: z.literal(undefined).catch(undefined), + url: v.fallback(v.undefined(), undefined), + static_url: v.fallback(v.undefined(), undefined), accounts: filteredArray(accountSchema), account_ids: filteredArray(v.string()).catch([]), }); const customEmojiReactionSchema = baseEmojiReactionSchema.extend({ name: v.string(), - url: z.string().url(), - static_url: z.string().url(), + url: v.pipe(v.string(), v.url()), + static_url: v.pipe(v.string(), v.url()), }); /** diff --git a/packages/pl-api/lib/entities/featured-tag.ts b/packages/pl-api/lib/entities/featured-tag.ts index dfd7781c6..f07ad3e7b 100644 --- a/packages/pl-api/lib/entities/featured-tag.ts +++ b/packages/pl-api/lib/entities/featured-tag.ts @@ -5,8 +5,8 @@ const featuredTagSchema = v.object({ id: v.string(), name: v.string(), url: v.fallback(v.optional(v.string()), undefined), - statuses_count: z.number(), - last_status_at: z.number(), + statuses_count: v.number(), + last_status_at: v.number(), }); type FeaturedTag = v.InferOutput; diff --git a/packages/pl-api/lib/entities/filter-result.ts b/packages/pl-api/lib/entities/filter-result.ts index 36b7c1b42..fcd64be77 100644 --- a/packages/pl-api/lib/entities/filter-result.ts +++ b/packages/pl-api/lib/entities/filter-result.ts @@ -5,8 +5,8 @@ import { filterSchema } from './filter'; /** @see {@link https://docs.joinmastodon.org/entities/FilterResult/} */ const filterResultSchema = v.object({ filter: filterSchema, - keyword_matches: z.array(v.string()).nullable().catch(null), - status_matches: z.array(v.string()).nullable().catch(null), + keyword_matches: v.fallback(v.nullable(v.string()), null), + status_matches: v.fallback(v.nullable(v.string()), null), }); type FilterResult = v.InferOutput; diff --git a/packages/pl-api/lib/entities/filter.ts b/packages/pl-api/lib/entities/filter.ts index 7e27850a4..5301800f5 100644 --- a/packages/pl-api/lib/entities/filter.ts +++ b/packages/pl-api/lib/entities/filter.ts @@ -33,9 +33,9 @@ const filterSchema = z.preprocess((filter: any) => { }, v.object({ id: v.string(), title: v.string(), - context: z.array(z.enum(['home', 'notifications', 'public', 'thread', 'account'])), - expires_at: z.string().datetime({ offset: true }).nullable().catch(null), - filter_action: z.enum(['warn', 'hide']).catch('warn'), + context: v.array(v.picklist(['home', 'notifications', 'public', 'thread', 'account'])), + expires_at: v.fallback(v.nullable(z.string().datetime({ offset: true })), null), + filter_action: v.fallback(v.picklist(['warn', 'hide']), 'warn'), keywords: filteredArray(filterKeywordSchema), statuses: filteredArray(filterStatusSchema), })); diff --git a/packages/pl-api/lib/entities/group.ts b/packages/pl-api/lib/entities/group.ts index 31e5e60bf..ee8f8bed8 100644 --- a/packages/pl-api/lib/entities/group.ts +++ b/packages/pl-api/lib/entities/group.ts @@ -17,10 +17,10 @@ const groupSchema = v.object({ locked: v.fallback(v.boolean(), false), membership_required: v.fallback(v.boolean(), false), members_count: v.fallback(v.number(), 0), - owner: v.object({ id: z.string() }).nullable().catch(null), + owner: v.object({ id: v.string() }).nullable().catch(null), note: z.string().transform(note => note === '

' ? '' : note).catch(''), relationship: v.fallback(v.nullable(groupRelationshipSchema), null), // Dummy field to be overwritten later - statuses_visibility: z.string().catch('public'), + statuses_visibility: v.fallback(v.string(), 'public'), uri: v.fallback(v.string(), ''), url: v.fallback(v.string(), ''), diff --git a/packages/pl-api/lib/entities/instance.ts b/packages/pl-api/lib/entities/instance.ts index b6950f262..4a1ebdb40 100644 --- a/packages/pl-api/lib/entities/instance.ts +++ b/packages/pl-api/lib/entities/instance.ts @@ -108,47 +108,47 @@ const fixVersion = (version: string) => { }; const configurationSchema = coerceObject({ - accounts: v.object({ + accounts: v.fallback(v.nullable(v.object({ allow_custom_css: v.boolean(), - max_featured_tags: z.number().int(), - max_profile_fields: z.number().int(), - }).nullable().catch(null), + max_featured_tags: v.pipe(v.number(), v.integer()), + max_profile_fields: v.pipe(v.number(), v.integer()), + })), null), chats: coerceObject({ - max_characters: z.number().catch(5000), + max_characters: v.fallback(v.number(), 5000), }), groups: coerceObject({ - max_characters_description: z.number().catch(160), - max_characters_name: z.number().catch(50), + max_characters_description: v.fallback(v.number(), 160), + max_characters_name: v.fallback(v.number(), 50), }), media_attachments: coerceObject({ - image_matrix_limit: z.number().optional().catch(undefined), - image_size_limit: z.number().optional().catch(undefined), - supported_mime_types: mimeSchema.array().optional().catch(undefined), - video_duration_limit: z.number().optional().catch(undefined), - video_frame_rate_limit: z.number().optional().catch(undefined), - video_matrix_limit: z.number().optional().catch(undefined), - video_size_limit: z.number().optional().catch(undefined), + image_matrix_limit: v.fallback(v.optional(v.number()), undefined), + image_size_limit: v.fallback(v.optional(v.number()), undefined), + supported_mime_types: v.fallback(v.optional(v.array(mimeSchema)), undefined), + video_duration_limit: v.fallback(v.optional(v.number()), undefined), + video_frame_rate_limit: v.fallback(v.optional(v.number()), undefined), + video_matrix_limit: v.fallback(v.optional(v.number()), undefined), + video_size_limit: v.fallback(v.optional(v.number()), undefined), }), polls: coerceObject({ - max_characters_per_option: z.number().catch(25), - max_expiration: z.number().catch(2629746), - max_options: z.number().catch(4), - min_expiration: z.number().catch(300), + max_characters_per_option: v.fallback(v.number(), 25), + max_expiration: v.fallback(v.number(), 2629746), + max_options: v.fallback(v.number(), 4), + min_expiration: v.fallback(v.number(), 300), }), reactions: coerceObject({ max_reactions: v.fallback(v.number(), 0), }), statuses: coerceObject({ - characters_reserved_per_url: z.number().optional().catch(undefined), - max_characters: z.number().catch(500), - max_media_attachments: z.number().catch(4), + characters_reserved_per_url: v.fallback(v.optional(v.number()), undefined), + max_characters: v.fallback(v.number(), 500), + max_media_attachments: v.fallback(v.number(), 4), }), translation: coerceObject({ enabled: v.fallback(v.boolean(), false), }), urls: coerceObject({ - streaming: z.string().url().optional().catch(undefined), + streaming: v.fallback(v.optional(v.pipe(v.string(), v.url())), undefined), }), vapid: coerceObject({ public_key: v.fallback(v.string(), ''), @@ -156,8 +156,8 @@ const configurationSchema = coerceObject({ }); const contactSchema = coerceObject({ - contact_account: accountSchema.optional().catch(undefined), - email: z.string().email().catch(''), + contact_account: v.fallback(v.optional(accountSchema), undefined), + email: v.fallback(v.pipe(v.string(), v.email()), ''), }); const pleromaSchema = coerceObject({ @@ -165,11 +165,11 @@ const pleromaSchema = coerceObject({ account_activation_required: v.fallback(v.boolean(), false), birthday_min_age: v.fallback(v.number(), 0), birthday_required: v.fallback(v.boolean(), false), - description_limit: z.number().catch(1500), + description_limit: v.fallback(v.number(), 1500), features: v.fallback(v.array(v.string()), []), federation: coerceObject({ enabled: v.fallback(v.boolean(), true), // Assume true unless explicitly false - mrf_policies: z.string().array().optional().catch(undefined), + mrf_policies: v.fallback(v.optional(v.array(v.string())), undefined), mrf_simple: coerceObject({ accept: v.fallback(v.array(v.string()), []), avatar_removal: v.fallback(v.array(v.string()), []), @@ -192,17 +192,15 @@ const pleromaSchema = coerceObject({ allow_headings: v.fallback(v.boolean(), false), allow_inline_images: v.fallback(v.boolean(), false), }), - migration_cooldown_period: z.number().optional().catch(undefined), + migration_cooldown_period: v.fallback(v.optional(v.number()), undefined), multitenancy: coerceObject({ - domains: z - .array( - v.object({ - domain: z.coerce.string(), - id: v.string(), - public: v.fallback(v.boolean(), false), - }), - ) - .optional(), + domains: v.optional(v.array( + v.object({ + domain: z.coerce.string(), + id: v.string(), + public: v.fallback(v.boolean(), false), + }), + )), enabled: v.fallback(v.boolean(), false), }), post_formats: z.string().array().optional().catch(undefined), @@ -230,16 +228,16 @@ const pleromaSchema = coerceObject({ }), oauth_consumer_strategies: v.fallback(v.array(v.string()), []), stats: coerceObject({ - mau: z.number().optional().catch(undefined), + mau: v.fallback(v.optional(v.number()), undefined), }), vapid_public_key: v.fallback(v.string(), ''), }); const pleromaPollLimitsSchema = coerceObject({ - max_expiration: z.number().optional().catch(undefined), - max_option_chars: z.number().optional().catch(undefined), - max_options: z.number().optional().catch(undefined), - min_expiration: z.number().optional().catch(undefined), + max_expiration: v.fallback(v.optional(v.number()), undefined), + max_option_chars: v.fallback(v.optional(v.number()), undefined), + max_options: v.fallback(v.optional(v.number()), undefined), + min_expiration: v.fallback(v.optional(v.number()), undefined), }); const registrations = coerceObject({ @@ -249,9 +247,9 @@ const registrations = coerceObject({ }); const statsSchema = coerceObject({ - domain_count: z.number().optional().catch(undefined), - status_count: z.number().optional().catch(undefined), - user_count: z.number().optional().catch(undefined), + domain_count: v.fallback(v.optional(v.number()), undefined), + status_count: v.fallback(v.optional(v.number()), undefined), + user_count: v.fallback(v.optional(v.number()), undefined), }); const thumbnailSchema = coerceObject({ @@ -268,15 +266,15 @@ const instanceV1Schema = coerceObject({ account_domain: v.fallback(v.string(), ''), approval_required: v.fallback(v.boolean(), false), configuration: configurationSchema, - contact_account: accountSchema.optional().catch(undefined), + contact_account: v.fallback(v.optional(accountSchema), undefined), description: v.fallback(v.string(), ''), - description_limit: z.number().catch(1500), - email: z.string().email().catch(''), + description_limit: v.fallback(v.number(), 1500), + email: v.fallback(v.pipe(v.string(), v.email()), ''), feature_quote: v.fallback(v.boolean(), false), - fedibird_capabilities: z.array(v.string()).catch([]), + fedibird_capabilities: v.fallback(v.array(v.string()), []), languages: v.fallback(v.array(v.string()), []), - max_media_attachments: z.number().optional().catch(undefined), - max_toot_chars: z.number().optional().catch(undefined), + max_media_attachments: v.fallback(v.optional(v.number()), undefined), + max_toot_chars: v.fallback(v.optional(v.number()), undefined), pleroma: pleromaSchema, poll_limits: pleromaPollLimitsSchema, registrations: v.fallback(v.boolean(), false), @@ -285,13 +283,13 @@ const instanceV1Schema = coerceObject({ stats: statsSchema, thumbnail: v.fallback(v.string(), ''), title: v.fallback(v.string(), ''), - upload_limit: z.number().optional().catch(undefined), + upload_limit: v.fallback(v.optional(v.number()), undefined), uri: v.fallback(v.string(), ''), urls: coerceObject({ - streaming_api: z.string().url().optional().catch(undefined), + streaming_api: v.fallback(v.optional(v.pipe(v.string(), v.url())), undefined), }), usage: usageSchema, - version: z.string().catch('0.0.0'), + version: v.fallback(v.string(), '0.0.0'), }); /** @see {@link https://docs.joinmastodon.org/entities/Instance/} */ @@ -308,13 +306,13 @@ const instanceSchema = z.preprocess((data: any) => { return instanceV1ToV2({ ...data, api_versions: apiVersions }); }, coerceObject({ account_domain: v.fallback(v.string(), ''), - api_versions: z.record(z.number()).catch({}), + api_versions: v.fallback(v.record(v.string(), v.number()), {}), configuration: configurationSchema, contact: contactSchema, description: v.fallback(v.string(), ''), domain: v.fallback(v.string(), ''), feature_quote: v.fallback(v.boolean(), false), - fedibird_capabilities: z.array(v.string()).catch([]), + fedibird_capabilities: v.fallback(v.array(v.string()), []), languages: v.fallback(v.array(v.string()), []), pleroma: pleromaSchema, registrations: registrations, @@ -323,7 +321,7 @@ const instanceSchema = z.preprocess((data: any) => { thumbnail: thumbnailSchema, title: v.fallback(v.string(), ''), usage: usageSchema, - version: z.string().catch('0.0.0'), + version: v.fallback(v.string(), '0.0.0'), }).transform((instance) => { const version = fixVersion(instance.version); diff --git a/packages/pl-api/lib/entities/interaction-policy.ts b/packages/pl-api/lib/entities/interaction-policy.ts index 975743c92..d76b064e2 100644 --- a/packages/pl-api/lib/entities/interaction-policy.ts +++ b/packages/pl-api/lib/entities/interaction-policy.ts @@ -2,11 +2,11 @@ import * as v from 'valibot'; import { coerceObject } from './utils'; -const interactionPolicyEntrySchema = z.enum(['public', 'followers', 'following', 'mutuals', 'mentioned', 'author', 'me']); +const interactionPolicyEntrySchema = v.picklist(['public', 'followers', 'following', 'mutuals', 'mentioned', 'author', 'me']); const interactionPolicyRuleSchema = coerceObject({ - always: z.array(interactionPolicyEntrySchema).default(['public']), - with_approval: z.array(interactionPolicyEntrySchema).default([]), + always: v.fallback(v.array(interactionPolicyEntrySchema), ['public']), + with_approval: v.fallback(v.array(interactionPolicyEntrySchema), []), }); /** @see {@link https://docs.gotosocial.org/en/latest/api/swagger/} */ diff --git a/packages/pl-api/lib/entities/interaction-request.ts b/packages/pl-api/lib/entities/interaction-request.ts index 92b70a1f1..b75288846 100644 --- a/packages/pl-api/lib/entities/interaction-request.ts +++ b/packages/pl-api/lib/entities/interaction-request.ts @@ -5,14 +5,14 @@ import { statusSchema } from './status'; /** @see {@link https://docs.gotosocial.org/en/latest/api/swagger.yaml#/definitions/interactionRequest} */ const interactionRequestSchema = v.object({ - accepted_at: z.string().datetime().nullable().catch(null), + accepted_at: v.fallback(v.nullable(z.string().datetime()), null), account: accountSchema, created_at: z.string().datetime(), id: v.string(), - rejected_at: z.string().datetime().nullable().catch(null), + rejected_at: v.fallback(v.nullable(z.string().datetime()), null), reply: v.fallback(v.nullable(statusSchema), null), status: v.fallback(v.nullable(statusSchema), null), - type: z.enum(['favourite', 'reply', 'reblog']), + type: v.picklist(['favourite', 'reply', 'reblog']), uri: v.fallback(v.nullable(v.string()), null), }); diff --git a/packages/pl-api/lib/entities/location.ts b/packages/pl-api/lib/entities/location.ts index b9391c444..37015600d 100644 --- a/packages/pl-api/lib/entities/location.ts +++ b/packages/pl-api/lib/entities/location.ts @@ -1,7 +1,7 @@ import * as v from 'valibot'; const locationSchema = v.object({ - url: z.string().url().catch(''), + url: v.fallback(v.pipe(v.string(), v.url()), ''), description: v.fallback(v.string(), ''), country: v.fallback(v.string(), ''), locality: v.fallback(v.string(), ''), @@ -12,10 +12,10 @@ const locationSchema = v.object({ origin_provider: v.fallback(v.string(), ''), type: v.fallback(v.string(), ''), timezone: v.fallback(v.string(), ''), - geom: v.object({ - coordinates: z.tuple([z.number(), z.number()]).nullable().catch(null), + geom: v.fallback(v.nullable(v.object({ + coordinates: v.fallback(v.nullable(z.tuple([v.number(), v.number()])), null), srid: v.fallback(v.string(), ''), - }).nullable().catch(null), + })), null), }); type Location = v.InferOutput; diff --git a/packages/pl-api/lib/entities/marker.ts b/packages/pl-api/lib/entities/marker.ts index 597d88b12..07bb579e3 100644 --- a/packages/pl-api/lib/entities/marker.ts +++ b/packages/pl-api/lib/entities/marker.ts @@ -7,7 +7,7 @@ const markerSchema = z.preprocess((marker: any) => marker ? ({ ...marker, }) : null, v.object({ last_read_id: v.string(), - version: z.number().int(), + version: v.pipe(v.number(), v.integer()), updated_at: dateSchema, unread_count: z.number().int().optional().catch(undefined), })); @@ -15,7 +15,7 @@ const markerSchema = z.preprocess((marker: any) => marker ? ({ /** @see {@link https://docs.joinmastodon.org/entities/Marker/} */ type Marker = v.InferOutput; -const markersSchema = z.record(markerSchema); +const markersSchema = v.record(v.string(), markerSchema); type Markers = v.InferOutput; diff --git a/packages/pl-api/lib/entities/media-attachment.ts b/packages/pl-api/lib/entities/media-attachment.ts index 778e69eac..be24ac506 100644 --- a/packages/pl-api/lib/entities/media-attachment.ts +++ b/packages/pl-api/lib/entities/media-attachment.ts @@ -17,9 +17,9 @@ const blurhashSchema = z.string().superRefine((value, ctx) => { const baseAttachmentSchema = v.object({ id: v.string(), type: v.string(), - url: z.string().url().catch(''), - preview_url: z.string().url().catch(''), - remote_url: z.string().url().nullable().catch(null), + url: v.fallback(v.pipe(v.string(), v.url()), ''), + preview_url: v.fallback(v.pipe(v.string(), v.url()), ''), + remote_url: v.fallback(v.nullable(z.string().url()), null), description: v.fallback(v.string(), ''), blurhash: v.fallback(v.nullable(blurhashSchema), null), @@ -27,64 +27,64 @@ const baseAttachmentSchema = v.object({ }); const imageMetaSchema = v.object({ - width: z.number(), - height: z.number(), + width: v.number(), + height: v.number(), size: z.string().regex(/\d+x\d+$/).nullable().catch(null), aspect: v.fallback(v.nullable(v.number()), null), }); const imageAttachmentSchema = baseAttachmentSchema.extend({ - type: z.literal('image'), - meta: v.object({ - original: imageMetaSchema.optional().catch(undefined), - small: imageMetaSchema.optional().catch(undefined), - focus: v.object({ + type: v.literal('image'), + meta: v.fallback(v.object({ + original: v.fallback(v.optional(imageMetaSchema), undefined), + small: v.fallback(v.optional(imageMetaSchema), undefined), + focus: v.fallback(v.optional(v.object({ x: z.number().min(-1).max(1), y: z.number().min(-1).max(1), - }).optional().catch(undefined), - }).catch({}), + })), undefined), + }), {}), }); const videoAttachmentSchema = baseAttachmentSchema.extend({ - type: z.literal('video'), - meta: v.object({ - duration: z.number().optional().catch(undefined), - original: imageMetaSchema.extend({ - frame_rate: z.string().regex(/\d+\/\d+$/).nullable().catch(null), - duration: z.number().nonnegative().nullable().catch(null), - }).optional().catch(undefined), - small: imageMetaSchema.optional().catch(undefined), + type: v.literal('video'), + meta: v.fallback(v.object({ + duration: v.fallback(v.optional(v.number()), undefined), + original: v.fallback(v.optional(imageMetaSchema.extend({ + 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), + small: v.fallback(v.optional(imageMetaSchema), undefined), // WIP: add rest - }).catch({}), + }), {}), }); const gifvAttachmentSchema = baseAttachmentSchema.extend({ - type: z.literal('gifv'), - meta: v.object({ - duration: z.number().optional().catch(undefined), - original: imageMetaSchema.optional().catch(undefined), - }).catch({}), + type: v.literal('gifv'), + meta: v.fallback(v.object({ + duration: v.fallback(v.optional(v.number()), undefined), + original: v.fallback(v.optional(imageMetaSchema), undefined), + }), {}), }); const audioAttachmentSchema = baseAttachmentSchema.extend({ - type: z.literal('audio'), - meta: v.object({ - duration: z.number().optional().catch(undefined), - colors: v.object({ + type: v.literal('audio'), + meta: v.fallback(v.object({ + duration: v.fallback(v.optional(v.number()), undefined), + colors: v.fallback(v.optional(v.object({ background: v.fallback(v.optional(v.string()), undefined), foreground: v.fallback(v.optional(v.string()), undefined), accent: v.fallback(v.optional(v.string()), undefined), - duration: z.number().optional().catch(undefined), - }).optional().catch(undefined), - original: v.object({ - duration: z.number().optional().catch(undefined), + duration: v.fallback(v.optional(v.number()), undefined), + })), undefined), + original: v.fallback(v.optional(v.object({ + duration: v.fallback(v.optional(v.number()), undefined), bitrate: z.number().nonnegative().optional().catch(undefined), - }).optional().catch(undefined), - }).catch({}), + })), undefined), + }), {}), }); const unknownAttachmentSchema = baseAttachmentSchema.extend({ - type: z.literal('unknown'), + type: v.literal('unknown'), }); /** @see {@link https://docs.joinmastodon.org/entities/MediaAttachment} */ diff --git a/packages/pl-api/lib/entities/mention.ts b/packages/pl-api/lib/entities/mention.ts index 99a933d42..19cb4132b 100644 --- a/packages/pl-api/lib/entities/mention.ts +++ b/packages/pl-api/lib/entities/mention.ts @@ -4,7 +4,7 @@ import * as v from 'valibot'; const mentionSchema = v.object({ id: v.string(), username: v.fallback(v.string(), ''), - url: z.string().url().catch(''), + url: v.fallback(v.pipe(v.string(), v.url()), ''), acct: v.string(), }).transform((mention) => { if (!mention.username) { diff --git a/packages/pl-api/lib/entities/notification-policy.ts b/packages/pl-api/lib/entities/notification-policy.ts index a4400cf81..ed964d15d 100644 --- a/packages/pl-api/lib/entities/notification-policy.ts +++ b/packages/pl-api/lib/entities/notification-policy.ts @@ -7,8 +7,8 @@ const notificationPolicySchema = v.object({ filter_new_accounts: v.boolean(), filter_private_mentions: v.boolean(), summary: v.object({ - pending_requests_count: z.number().int(), - pending_notifications_count: z.number().int(), + pending_requests_count: v.pipe(v.number(), v.integer()), + pending_notifications_count: v.pipe(v.number(), v.integer()), }), }); diff --git a/packages/pl-api/lib/entities/notification-request.ts b/packages/pl-api/lib/entities/notification-request.ts index 51ea8c841..75372ec7b 100644 --- a/packages/pl-api/lib/entities/notification-request.ts +++ b/packages/pl-api/lib/entities/notification-request.ts @@ -11,7 +11,7 @@ const notificationRequestSchema = v.object({ updated_at: dateSchema, account: accountSchema, notifications_count: z.coerce.string(), - last_status: statusSchema.optional().catch(undefined), + last_status: v.fallback(v.optional(statusSchema), undefined), }); type NotificationRequest = v.InferOutput; diff --git a/packages/pl-api/lib/entities/notification.ts b/packages/pl-api/lib/entities/notification.ts index 633ad6e8b..128344dcf 100644 --- a/packages/pl-api/lib/entities/notification.ts +++ b/packages/pl-api/lib/entities/notification.ts @@ -21,54 +21,54 @@ const baseNotificationSchema = v.object({ }); const accountNotificationSchema = baseNotificationSchema.extend({ - type: z.enum(['follow', 'follow_request', 'admin.sign_up', 'bite']), + type: v.picklist(['follow', 'follow_request', 'admin.sign_up', 'bite']), }); const mentionNotificationSchema = baseNotificationSchema.extend({ - type: z.literal('mention'), - subtype: z.enum(['reply']).nullable().catch(null), + type: v.literal('mention'), + subtype: v.fallback(v.nullable(v.picklist(['reply'])), null), status: statusSchema, }); const statusNotificationSchema = baseNotificationSchema.extend({ - type: z.enum(['status', 'reblog', 'favourite', 'poll', 'update', 'event_reminder']), + type: v.picklist(['status', 'reblog', 'favourite', 'poll', 'update', 'event_reminder']), status: statusSchema, }); const reportNotificationSchema = baseNotificationSchema.extend({ - type: z.literal('admin.report'), + type: v.literal('admin.report'), report: reportSchema, }); const severedRelationshipNotificationSchema = baseNotificationSchema.extend({ - type: z.literal('severed_relationships'), + type: v.literal('severed_relationships'), relationship_severance_event: relationshipSeveranceEventSchema, }); const moderationWarningNotificationSchema = baseNotificationSchema.extend({ - type: z.literal('moderation_warning'), + type: v.literal('moderation_warning'), moderation_warning: accountWarningSchema, }); const moveNotificationSchema = baseNotificationSchema.extend({ - type: z.literal('move'), + type: v.literal('move'), target: accountSchema, }); const emojiReactionNotificationSchema = baseNotificationSchema.extend({ - type: z.literal('emoji_reaction'), + type: v.literal('emoji_reaction'), emoji: v.string(), emoji_url: v.fallback(v.nullable(v.string()), null), status: statusSchema, }); const chatMentionNotificationSchema = baseNotificationSchema.extend({ - type: z.literal('chat_mention'), + type: v.literal('chat_mention'), chat_message: chatMessageSchema, }); const eventParticipationRequestNotificationSchema = baseNotificationSchema.extend({ - type: z.enum(['participation_accepted', 'participation_request']), + type: v.picklist(['participation_accepted', 'participation_request']), status: statusSchema, participation_message: v.fallback(v.nullable(v.string()), null), }); diff --git a/packages/pl-api/lib/entities/oauth-token.ts b/packages/pl-api/lib/entities/oauth-token.ts index cf9ea0931..cd97b9aac 100644 --- a/packages/pl-api/lib/entities/oauth-token.ts +++ b/packages/pl-api/lib/entities/oauth-token.ts @@ -6,7 +6,7 @@ const oauthTokenSchema = z.preprocess((token: any) => ({ valid_until: token?.valid_until?.padEnd(27, 'Z'), }), v.object({ app_name: v.string(), - id: z.number(), + id: v.number(), valid_until: z.string().datetime({ offset: true }), })); diff --git a/packages/pl-api/lib/entities/poll.ts b/packages/pl-api/lib/entities/poll.ts index 923dc3a9a..1862db118 100644 --- a/packages/pl-api/lib/entities/poll.ts +++ b/packages/pl-api/lib/entities/poll.ts @@ -7,20 +7,20 @@ const pollOptionSchema = v.object({ title: v.fallback(v.string(), ''), votes_count: v.fallback(v.number(), 0), - title_map: z.record(v.string()).nullable().catch(null), + title_map: v.record(v.string(), v.string()).nullable().catch(null), }); /** @see {@link https://docs.joinmastodon.org/entities/Poll/} */ const pollSchema = v.object({ emojis: filteredArray(customEmojiSchema), expired: v.fallback(v.boolean(), false), - expires_at: z.string().datetime().nullable().catch(null), + expires_at: v.fallback(v.nullable(z.string().datetime()), null), id: v.string(), multiple: v.fallback(v.boolean(), false), options: z.array(pollOptionSchema).min(2), voters_count: v.fallback(v.number(), 0), votes_count: v.fallback(v.number(), 0), - own_votes: z.array(z.number()).nonempty().nullable().catch(null), + own_votes: v.fallback(v.nullable(z.number()).nonempty(), null), voted: v.fallback(v.boolean(), false), non_anonymous: v.fallback(v.boolean(), false), diff --git a/packages/pl-api/lib/entities/preview-card.ts b/packages/pl-api/lib/entities/preview-card.ts index ad88f1f7f..04c4cdc9e 100644 --- a/packages/pl-api/lib/entities/preview-card.ts +++ b/packages/pl-api/lib/entities/preview-card.ts @@ -3,19 +3,19 @@ import * as v from 'valibot'; /** @see {@link https://docs.joinmastodon.org/entities/PreviewCard/} */ const previewCardSchema = v.object({ author_name: v.fallback(v.string(), ''), - author_url: z.string().url().catch(''), + author_url: v.fallback(v.pipe(v.string(), v.url()), ''), blurhash: v.fallback(v.nullable(v.string()), null), description: v.fallback(v.string(), ''), - embed_url: z.string().url().catch(''), + embed_url: v.fallback(v.pipe(v.string(), v.url()), ''), height: v.fallback(v.number(), 0), html: v.fallback(v.string(), ''), image: v.fallback(v.nullable(v.string()), null), image_description: v.fallback(v.string(), ''), provider_name: v.fallback(v.string(), ''), - provider_url: z.string().url().catch(''), + provider_url: v.fallback(v.pipe(v.string(), v.url()), ''), title: v.fallback(v.string(), ''), - type: z.enum(['link', 'photo', 'video', 'rich']).catch('link'), - url: z.string().url(), + type: v.fallback(v.picklist(['link', 'photo', 'video', 'rich']), 'link'), + url: v.pipe(v.string(), v.url()), width: v.fallback(v.number(), 0), }); diff --git a/packages/pl-api/lib/entities/relationship-severance-event.ts b/packages/pl-api/lib/entities/relationship-severance-event.ts index 42970bf76..6ac0430ef 100644 --- a/packages/pl-api/lib/entities/relationship-severance-event.ts +++ b/packages/pl-api/lib/entities/relationship-severance-event.ts @@ -5,9 +5,9 @@ import { dateSchema } from './utils'; /** @see {@link https://docs.joinmastodon.org/entities/RelationshipSeveranceEvent/} */ const relationshipSeveranceEventSchema = v.object({ id: v.string(), - type: z.enum(['domain_block', 'user_domain_block', 'account_suspension']), + type: v.picklist(['domain_block', 'user_domain_block', 'account_suspension']), purged: v.string(), - relationships_count: z.number().optional().catch(undefined), + relationships_count: v.fallback(v.optional(v.number()), undefined), created_at: dateSchema, }); diff --git a/packages/pl-api/lib/entities/report.ts b/packages/pl-api/lib/entities/report.ts index 96b7c9d30..f9ab65d9f 100644 --- a/packages/pl-api/lib/entities/report.ts +++ b/packages/pl-api/lib/entities/report.ts @@ -11,9 +11,9 @@ const reportSchema = v.object({ category: v.fallback(v.optional(v.string()), undefined), comment: v.fallback(v.optional(v.string()), undefined), forwarded: v.fallback(v.optional(v.boolean()), undefined), - created_at: dateSchema.optional().catch(undefined), - status_ids: z.array(v.string()).nullable().catch(null), - rule_ids: z.array(v.string()).nullable().catch(null), + created_at: v.fallback(v.optional(dateSchema), undefined), + status_ids: v.fallback(v.nullable(v.string()), null), + rule_ids: v.fallback(v.nullable(v.string()), null), target_account: v.fallback(v.nullable(accountSchema), null), }); diff --git a/packages/pl-api/lib/entities/scheduled-status.ts b/packages/pl-api/lib/entities/scheduled-status.ts index 93ecbf9f7..a0ce78995 100644 --- a/packages/pl-api/lib/entities/scheduled-status.ts +++ b/packages/pl-api/lib/entities/scheduled-status.ts @@ -15,14 +15,14 @@ const scheduledStatusSchema = v.object({ multiple: v.fallback(v.optional(v.boolean()), undefined), hide_totals: v.fallback(v.optional(v.boolean()), undefined), }).nullable().catch(null), - media_ids: z.array(v.string()).nullable().catch(null), - sensitive: z.coerce.boolean().nullable().catch(null), + media_ids: v.fallback(v.nullable(v.string()), null), + sensitive: v.fallback(v.nullable(z.coerce.boolean()), null), spoiler_text: v.fallback(v.nullable(v.string()), null), visibility: z.string().catch('public'), in_reply_to_id: v.fallback(v.nullable(v.string()), null), language: v.fallback(v.nullable(v.string()), null), - application_id: z.number().int().nullable().catch(null), - scheduled_at: z.string().datetime({ offset: true }).nullable().catch(null), + application_id: v.fallback(v.nullable(z.number().int()), null), + scheduled_at: v.fallback(v.nullable(z.string().datetime({ offset: true })), null), idempotency: v.fallback(v.nullable(v.string()), null), with_rate_limit: v.fallback(v.boolean(), false), diff --git a/packages/pl-api/lib/entities/status-source.ts b/packages/pl-api/lib/entities/status-source.ts index d811d3092..01516a200 100644 --- a/packages/pl-api/lib/entities/status-source.ts +++ b/packages/pl-api/lib/entities/status-source.ts @@ -11,8 +11,8 @@ const statusSourceSchema = v.object({ content_type: z.string().catch('text/plain'), location: v.fallback(v.nullable(locationSchema), null), - text_map: z.record(v.string()).nullable().catch(null), - spoiler_text_map: z.record(v.string()).nullable().catch(null), + text_map: v.record(v.string(), v.string()).nullable().catch(null), + spoiler_text_map: v.record(v.string(), v.string()).nullable().catch(null), }); type StatusSource = v.InferOutput; diff --git a/packages/pl-api/lib/entities/status.ts b/packages/pl-api/lib/entities/status.ts index 4abbed09f..8d3196965 100644 --- a/packages/pl-api/lib/entities/status.ts +++ b/packages/pl-api/lib/entities/status.ts @@ -17,13 +17,13 @@ import { dateSchema, filteredArray } from './utils'; const statusEventSchema = v.object({ name: v.fallback(v.string(), ''), - start_time: z.string().datetime().nullable().catch(null), - end_time: z.string().datetime().nullable().catch(null), - join_mode: z.enum(['free', 'restricted', 'invite']).nullable().catch(null), + start_time: v.fallback(v.nullable(z.string().datetime()), null), + end_time: v.fallback(v.nullable(z.string().datetime()), null), + join_mode: v.fallback(v.nullable(v.picklist(['free', 'restricted', 'invite'])), null), participants_count: v.fallback(v.number(), 0), - location: v.object({ + location: v.fallback(v.nullable(v.object({ name: v.fallback(v.string(), ''), - url: z.string().url().catch(''), + url: v.fallback(v.pipe(v.string(), v.url()), ''), latitude: v.fallback(v.number(), 0), longitude: v.fallback(v.number(), 0), street: v.fallback(v.string(), ''), @@ -31,14 +31,14 @@ const statusEventSchema = v.object({ locality: v.fallback(v.string(), ''), region: v.fallback(v.string(), ''), country: v.fallback(v.string(), ''), - }).nullable().catch(null), - join_state: z.enum(['pending', 'reject', 'accept']).nullable().catch(null), + })), null), + join_state: v.fallback(v.nullable(v.picklist(['pending', 'reject', 'accept'])), null), }); /** @see {@link https://docs.joinmastodon.org/entities/Status/} */ const baseStatusSchema = v.object({ id: v.string(), - uri: z.string().url().catch(''), + uri: v.fallback(v.pipe(v.string(), v.url()), ''), created_at: dateSchema, account: accountSchema, content: v.fallback(v.string(), ''), @@ -46,55 +46,55 @@ const baseStatusSchema = v.object({ sensitive: z.coerce.boolean(), spoiler_text: v.fallback(v.string(), ''), media_attachments: filteredArray(mediaAttachmentSchema), - application: v.object({ + application: v.fallback(v.nullable(v.object({ name: v.string(), - website: z.string().url().nullable().catch(null), - }).nullable().catch(null), + website: v.fallback(v.nullable(z.string().url()), null), + })), null), mentions: filteredArray(mentionSchema), tags: filteredArray(tagSchema), emojis: filteredArray(customEmojiSchema), reblogs_count: v.fallback(v.number(), 0), favourites_count: v.fallback(v.number(), 0), replies_count: v.fallback(v.number(), 0), - url: z.string().url().catch(''), + url: v.fallback(v.pipe(v.string(), v.url()), ''), in_reply_to_id: v.fallback(v.nullable(v.string()), null), in_reply_to_account_id: v.fallback(v.nullable(v.string()), null), poll: v.fallback(v.nullable(pollSchema), null), card: v.fallback(v.nullable(previewCardSchema), null), language: v.fallback(v.nullable(v.string()), null), text: v.fallback(v.nullable(v.string()), null), - edited_at: z.string().datetime().nullable().catch(null), + edited_at: v.fallback(v.nullable(z.string().datetime()), null), favourited: z.coerce.boolean(), reblogged: z.coerce.boolean(), muted: z.coerce.boolean(), bookmarked: z.coerce.boolean(), pinned: z.coerce.boolean(), filtered: filteredArray(filterResultSchema), - approval_status: z.enum(['pending', 'approval', 'rejected']).nullable().catch(null), + approval_status: v.fallback(v.nullable(v.picklist(['pending', 'approval', 'rejected'])), null), group: v.fallback(v.nullable(groupSchema), null), - scheduled_at: z.null().catch(null), + scheduled_at: v.fallback(v.null(), null), quote_id: v.fallback(v.nullable(v.string()), null), local: v.fallback(v.optional(v.boolean()), undefined), conversation_id: v.fallback(v.optional(v.string()), undefined), direct_conversation_id: v.fallback(v.optional(v.string()), undefined), in_reply_to_account_acct: v.fallback(v.optional(v.string()), undefined), - expires_at: z.string().datetime({ offset: true }).optional().catch(undefined), + expires_at: v.fallback(v.optional(z.string().datetime({ offset: true })), undefined), thread_muted: v.fallback(v.optional(v.boolean()), undefined), emoji_reactions: filteredArray(emojiReactionSchema), parent_visible: v.fallback(v.optional(v.boolean()), undefined), - pinned_at: z.string().datetime({ offset: true }).nullable().catch(null), + pinned_at: v.fallback(v.nullable(z.string().datetime({ offset: true })), null), quote_visible: v.fallback(v.optional(v.boolean()), undefined), quote_url: v.fallback(v.optional(v.string()), undefined), quotes_count: v.fallback(v.number(), 0), bookmark_folder: v.fallback(v.nullable(v.string()), null), event: v.fallback(v.nullable(statusEventSchema), null), - translation: translationSchema.nullable().or(z.literal(false)).catch(null), + translation: translationSchema.nullable().or(v.literal(false)).catch(null), - content_map: z.record(v.string()).nullable().catch(null), - text_map: z.record(v.string()).nullable().catch(null), - spoiler_text_map: z.record(v.string()).nullable().catch(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), + spoiler_text_map: v.fallback(v.nullable(v.record(v.string(), v.string())), null), dislikes_count: v.fallback(v.number(), 0), disliked: z.coerce.boolean().catch(false), @@ -135,16 +135,16 @@ const preprocess = (status: any) => { }; const statusSchema: z.ZodType = z.preprocess(preprocess, baseStatusSchema.extend({ - reblog: z.lazy(() => statusSchema).nullable().catch(null), + reblog: v.fallback(v.nullable(z.lazy(() => statusSchema)), null), - quote: z.lazy(() => statusSchema).nullable().catch(null), + quote: v.fallback(v.nullable(z.lazy(() => statusSchema)), null), })) as any; const statusWithoutAccountSchema = z.preprocess(preprocess, baseStatusSchema.omit({ account: true }).extend({ account: v.fallback(v.nullable(accountSchema), null), - reblog: z.lazy(() => statusSchema).nullable().catch(null), + reblog: v.fallback(v.nullable(z.lazy(() => statusSchema)), null), - quote: z.lazy(() => statusSchema).nullable().catch(null), + quote: v.fallback(v.nullable(z.lazy(() => statusSchema)), null), })); type Status = v.InferOutput & { diff --git a/packages/pl-api/lib/entities/streaming-event.ts b/packages/pl-api/lib/entities/streaming-event.ts index 758fe2e31..cdd3ffabf 100644 --- a/packages/pl-api/lib/entities/streaming-event.ts +++ b/packages/pl-api/lib/entities/streaming-event.ts @@ -9,7 +9,7 @@ import { notificationSchema } from './notification'; import { statusSchema } from './status'; const followRelationshipUpdateSchema = v.object({ - state: z.enum(['follow_pending', 'follow_accept', 'follow_reject']), + state: v.picklist(['follow_pending', 'follow_accept', 'follow_reject']), follower: v.object({ id: v.string(), follower_count: v.fallback(v.nullable(v.number()), null), @@ -29,59 +29,59 @@ const baseStreamingEventSchema = v.object({ }); const statusStreamingEventSchema = baseStreamingEventSchema.extend({ - event: z.enum(['update', 'status.update']), + event: v.picklist(['update', 'status.update']), payload: z.preprocess((payload: any) => JSON.parse(payload), statusSchema), }); const stringStreamingEventSchema = baseStreamingEventSchema.extend({ - event: z.enum(['delete', 'announcement.delete']), + event: v.picklist(['delete', 'announcement.delete']), payload: v.string(), }); const notificationStreamingEventSchema = baseStreamingEventSchema.extend({ - event: z.literal('notification'), + event: v.literal('notification'), payload: z.preprocess((payload: any) => JSON.parse(payload), notificationSchema), }); const emptyStreamingEventSchema = baseStreamingEventSchema.extend({ - event: z.literal('filters_changed'), + event: v.literal('filters_changed'), }); const conversationStreamingEventSchema = baseStreamingEventSchema.extend({ - event: z.literal('conversation'), + event: v.literal('conversation'), payload: z.preprocess((payload: any) => JSON.parse(payload), conversationSchema), }); const announcementStreamingEventSchema = baseStreamingEventSchema.extend({ - event: z.literal('announcement'), + event: v.literal('announcement'), payload: z.preprocess((payload: any) => JSON.parse(payload), announcementSchema), }); const announcementReactionStreamingEventSchema = baseStreamingEventSchema.extend({ - event: z.literal('announcement.reaction'), + event: v.literal('announcement.reaction'), payload: z.preprocess((payload: any) => JSON.parse(payload), announcementReactionSchema), }); const chatUpdateStreamingEventSchema = baseStreamingEventSchema.extend({ - event: z.literal('chat_update'), + event: v.literal('chat_update'), payload: z.preprocess((payload: any) => JSON.parse(payload), chatSchema), }); const followRelationshipsUpdateStreamingEventSchema = baseStreamingEventSchema.extend({ - event: z.literal('follow_relationships_update'), + event: v.literal('follow_relationships_update'), payload: z.preprocess((payload: any) => JSON.parse(payload), followRelationshipUpdateSchema), }); const respondStreamingEventSchema = baseStreamingEventSchema.extend({ - event: z.literal('respond'), + event: v.literal('respond'), payload: z.preprocess((payload: any) => JSON.parse(payload), v.object({ type: v.string(), - result: z.enum(['success', 'ignored', 'error']), + result: v.picklist(['success', 'ignored', 'error']), })), }); const markerStreamingEventSchema = baseStreamingEventSchema.extend({ - event: z.literal('marker'), + event: v.literal('marker'), payload: z.preprocess((payload: any) => JSON.parse(payload), markersSchema), }); diff --git a/packages/pl-api/lib/entities/tag.ts b/packages/pl-api/lib/entities/tag.ts index fea9a679c..8c35bcc3b 100644 --- a/packages/pl-api/lib/entities/tag.ts +++ b/packages/pl-api/lib/entities/tag.ts @@ -9,8 +9,8 @@ const historySchema = v.object({ /** @see {@link https://docs.joinmastodon.org/entities/tag} */ const tagSchema = v.object({ name: z.string().min(1), - url: z.string().url().catch(''), - history: z.array(historySchema).nullable().catch(null), + url: v.fallback(v.pipe(v.string(), v.url()), ''), + history: v.fallback(v.nullable(historySchema), null), following: v.fallback(v.optional(v.boolean()), undefined), }); diff --git a/packages/pl-api/lib/entities/token.ts b/packages/pl-api/lib/entities/token.ts index 59d7316dd..3fff4e40b 100644 --- a/packages/pl-api/lib/entities/token.ts +++ b/packages/pl-api/lib/entities/token.ts @@ -5,11 +5,11 @@ const tokenSchema = v.object({ access_token: v.string(), token_type: v.string(), scope: v.string(), - created_at: z.number().optional().catch(undefined), + created_at: v.fallback(v.optional(v.number()), undefined), - id: z.number().optional().catch(undefined), + id: v.fallback(v.optional(v.number()), undefined), refresh_token: v.fallback(v.optional(v.string()), undefined), - expires_in: z.number().optional().catch(undefined), + expires_in: v.fallback(v.optional(v.number()), undefined), me: v.fallback(v.optional(v.string()), undefined), }); diff --git a/packages/pl-api/lib/entities/translation.ts b/packages/pl-api/lib/entities/translation.ts index b844818fb..4ccc310d1 100644 --- a/packages/pl-api/lib/entities/translation.ts +++ b/packages/pl-api/lib/entities/translation.ts @@ -31,7 +31,7 @@ const translationSchema = z.preprocess((translation: any) => { id: v.fallback(v.nullable(v.string()), null), content: v.fallback(v.string(), ''), spoiler_text: v.fallback(v.string(), ''), - poll: translationPollSchema.optional().catch(undefined), + poll: v.fallback(v.optional(translationPollSchema), undefined), media_attachments: filteredArray(translationMediaAttachment), detected_source_language: v.string(), provider: v.string(), diff --git a/packages/pl-api/lib/entities/trends-link.ts b/packages/pl-api/lib/entities/trends-link.ts index 227e10cb1..f43e73f0c 100644 --- a/packages/pl-api/lib/entities/trends-link.ts +++ b/packages/pl-api/lib/entities/trends-link.ts @@ -6,10 +6,10 @@ import { historySchema } from './tag'; /** @see {@link https://docs.joinmastodon.org/entities/PreviewCard/#trends-link} */ const trendsLinkSchema = z.preprocess((link: any) => ({ ...link, id: link.url }), v.object({ id: v.fallback(v.string(), ''), - url: z.string().url().catch(''), + url: v.fallback(v.pipe(v.string(), v.url()), ''), title: v.fallback(v.string(), ''), description: v.fallback(v.string(), ''), - type: z.enum(['link', 'photo', 'video', 'rich']).catch('link'), + type: v.fallback(v.picklist(['link', 'photo', 'video', 'rich']), 'link'), author_name: v.fallback(v.string(), ''), author_url: v.fallback(v.string(), ''), provider_name: v.fallback(v.string(), ''), @@ -21,7 +21,7 @@ const trendsLinkSchema = z.preprocess((link: any) => ({ ...link, id: link.url }) image_description: v.fallback(v.nullable(v.string()), null), embed_url: v.fallback(v.string(), ''), blurhash: v.fallback(v.nullable(blurhashSchema), null), - history: z.array(historySchema).nullable().catch(null), + history: v.fallback(v.nullable(historySchema), null), })); type TrendsLink = v.InferOutput; diff --git a/packages/pl-api/lib/entities/utils.ts b/packages/pl-api/lib/entities/utils.ts index 5ebab3a53..4bbadbc5c 100644 --- a/packages/pl-api/lib/entities/utils.ts +++ b/packages/pl-api/lib/entities/utils.ts @@ -14,13 +14,17 @@ const filteredArray = (schema: T) => )); /** Validates the string as an emoji. */ -const emojiSchema = z.string().refine((v) => /\p{Extended_Pictographic}|[\u{1F1E6}-\u{1F1FF}]{2}/u.test(v)); +const emojiSchema = v.pipe(v.string(), v.emoji()); /** MIME schema, eg `image/png`. */ -const mimeSchema = z.string().regex(/^\w+\/[-+.\w]+$/); +const mimeSchema = v.pipe(v.string(), v.regex(/^\w+\/[-+.\w]+$/)); -/** zod schema to force the value into an object, if it isn't already. */ -const coerceObject = (shape: T) => - v.object({}).passthrough().catch({}).pipe(z.object(shape)); +/** valibot schema to force the value into an object, if it isn't already. */ +const coerceObject = (shape: T) => + v.pipe( + v.any(), + v.transform((input) => typeof input === 'object' ? input : {}), + v.object(shape), + ); export { filteredArray, emojiSchema, dateSchema, mimeSchema, coerceObject }; diff --git a/packages/pl-api/lib/entities/web-push-subscription.ts b/packages/pl-api/lib/entities/web-push-subscription.ts index 24ad82665..5632ae40d 100644 --- a/packages/pl-api/lib/entities/web-push-subscription.ts +++ b/packages/pl-api/lib/entities/web-push-subscription.ts @@ -4,7 +4,7 @@ import * as v from 'valibot'; const webPushSubscriptionSchema = v.object({ id: z.coerce.string(), endpoint: v.string(), - alerts: z.record(z.boolean()), + alerts: v.record(v.string(), z.boolean()), server_key: v.string(), }); diff --git a/packages/pl-fe/src/schemas/utils.ts b/packages/pl-fe/src/schemas/utils.ts index d4038009e..775a349c6 100644 --- a/packages/pl-fe/src/schemas/utils.ts +++ b/packages/pl-fe/src/schemas/utils.ts @@ -20,7 +20,7 @@ const makeCustomEmojiMap = (customEmojis: CustomEmoji[]) => return result; }, {}); -/** zod schema to force the value into an object, if it isn't already. */ +/** valibot schema to force the value into an object, if it isn't already. */ const coerceObject = (shape: T) => v.pipe( v.any(),