diff --git a/lib/modules/datasource/packagist/schema.ts b/lib/modules/datasource/packagist/schema.ts index 9e15d5bf157edb1a94aad1d7806c94276b75d49e..166a668d5dd8c074831889d31702315521caee29 100644 --- a/lib/modules/datasource/packagist/schema.ts +++ b/lib/modules/datasource/packagist/schema.ts @@ -1,6 +1,7 @@ import is from '@sindresorhus/is'; import { z } from 'zod'; import { logger } from '../../../logger'; +import { looseObject, looseValue } from '../../../util/schema'; import type { Release, ReleaseResult } from '../types'; export const MinifiedArray = z.array(z.record(z.unknown())).transform((xs) => { @@ -44,32 +45,20 @@ export const ComposerRelease = z version: z.string(), }) .merge( - z - .object({ - homepage: z.string().nullable().catch(null), - source: z - .object({ - url: z.string(), - }) - .nullable() - .catch(null), - time: z.string().nullable().catch(null), - require: z - .object({ - php: z.string(), - }) - .nullable() - .catch(null), - }) - .partial() + looseObject({ + homepage: z.string(), + source: z.object({ url: z.string() }), + time: z.string(), + require: z.object({ php: z.string() }), + }) ); export type ComposerRelease = z.infer<typeof ComposerRelease>; export const ComposerReleases = z .union([ - z.array(ComposerRelease.nullable().catch(null)), + z.array(looseValue(ComposerRelease)), z - .record(ComposerRelease.nullable().catch(null)) + .record(looseValue(ComposerRelease)) .transform((map) => Object.values(map)), ]) .catch([]) diff --git a/lib/util/schema.spec.ts b/lib/util/schema.spec.ts index a8c4221144cd5a29165d08f9079e8b9bc24271c2..39096a1168a1f0beb05ca6eb39b705ccd7142a9a 100644 --- a/lib/util/schema.spec.ts +++ b/lib/util/schema.spec.ts @@ -151,4 +151,50 @@ describe('util/schema', () => { expect(called).toBeTrue(); }); }); + + describe('looseValue', () => { + it('parses value', () => { + const s = schema.looseValue(z.string()); + expect(s.parse('foobar')).toBe('foobar'); + }); + + it('falls back to null wrong value', () => { + const s = schema.looseValue(z.string()); + expect(s.parse(123)).toBeNull(); + }); + + it('runs callback for wrong elements', () => { + let called = false; + const s = schema.looseValue(z.string(), () => { + called = true; + }); + expect(s.parse(123)).toBeNull(); + expect(called).toBeTrue(); + }); + }); + + describe('looseObject', () => { + it('parses object', () => { + const s = schema.looseObject({ + foo: z.string(), + bar: z.number(), + }); + expect(s.parse({ foo: 'foo', bar: 123 })).toEqual({ + foo: 'foo', + bar: 123, + }); + }); + + it('drops wrong items', () => { + const s = schema.looseObject({ + foo: z.string(), + bar: z.number(), + baz: z.string(), + }); + expect(s.parse({ foo: 'foo', bar: 'bar' })).toEqual({ + foo: 'foo', + bar: null, + }); + }); + }); }); diff --git a/lib/util/schema.ts b/lib/util/schema.ts index 7b0756def41bea71eed2e0a4b03411ecf95f5a15..8d369a33b2155481b20cd2cb2c11a0c349e1b588 100644 --- a/lib/util/schema.ts +++ b/lib/util/schema.ts @@ -143,3 +143,32 @@ export function looseRecord<T extends z.ZodTypeAny>( return filteredRecord; } + +export function looseValue<T, U extends z.ZodTypeDef, V>( + schema: z.ZodType<T, U, V>, + catchCallback?: () => void +): z.ZodCatch<z.ZodNullable<z.ZodType<T, U, V>>> { + const nullableSchema = schema.nullable(); + const schemaWithFallback = catchCallback + ? nullableSchema.catch(() => { + catchCallback(); + return null; + }) + : nullableSchema.catch(null); + return schemaWithFallback; +} + +export function looseObject<T extends z.ZodRawShape>( + shape: T +): z.ZodObject<{ + [k in keyof T]: z.ZodOptional<z.ZodCatch<z.ZodNullable<T[k]>>>; +}> { + const newShape: Record<keyof T, z.ZodTypeAny> = { ...shape }; + const keys: (keyof T)[] = Object.keys(shape); + for (const k of keys) { + const v = looseValue(shape[k]); + newShape[k] = v; + } + + return z.object(newShape).partial() as never; +}