From edef60045c48030a611fbdfbf7d970a86f4126c2 Mon Sep 17 00:00:00 2001 From: Sergei Zharinov <zharinov@users.noreply.github.com> Date: Wed, 22 Feb 2023 17:45:26 +0300 Subject: [PATCH] feat(schema): Add `looseValue` and `looseObject` helpers (#20576) --- lib/modules/datasource/packagist/schema.ts | 29 +++++--------- lib/util/schema.spec.ts | 46 ++++++++++++++++++++++ lib/util/schema.ts | 29 ++++++++++++++ 3 files changed, 84 insertions(+), 20 deletions(-) diff --git a/lib/modules/datasource/packagist/schema.ts b/lib/modules/datasource/packagist/schema.ts index 9e15d5bf15..166a668d5d 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 a8c4221144..39096a1168 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 7b0756def4..8d369a33b2 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; +} -- GitLab