feat(schema): Add looseValue and looseObject helpers (#20576)

This commit is contained in:
Sergei Zharinov 2023-02-22 17:45:26 +03:00 committed by GitHub
parent 340a913a48
commit edef60045c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 84 additions and 20 deletions

View file

@ -1,6 +1,7 @@
import is from '@sindresorhus/is'; import is from '@sindresorhus/is';
import { z } from 'zod'; import { z } from 'zod';
import { logger } from '../../../logger'; import { logger } from '../../../logger';
import { looseObject, looseValue } from '../../../util/schema';
import type { Release, ReleaseResult } from '../types'; import type { Release, ReleaseResult } from '../types';
export const MinifiedArray = z.array(z.record(z.unknown())).transform((xs) => { export const MinifiedArray = z.array(z.record(z.unknown())).transform((xs) => {
@ -44,32 +45,20 @@ export const ComposerRelease = z
version: z.string(), version: z.string(),
}) })
.merge( .merge(
z looseObject({
.object({ homepage: z.string(),
homepage: z.string().nullable().catch(null), source: z.object({ url: z.string() }),
source: z time: z.string(),
.object({ require: z.object({ php: z.string() }),
url: z.string(),
}) })
.nullable()
.catch(null),
time: z.string().nullable().catch(null),
require: z
.object({
php: z.string(),
})
.nullable()
.catch(null),
})
.partial()
); );
export type ComposerRelease = z.infer<typeof ComposerRelease>; export type ComposerRelease = z.infer<typeof ComposerRelease>;
export const ComposerReleases = z export const ComposerReleases = z
.union([ .union([
z.array(ComposerRelease.nullable().catch(null)), z.array(looseValue(ComposerRelease)),
z z
.record(ComposerRelease.nullable().catch(null)) .record(looseValue(ComposerRelease))
.transform((map) => Object.values(map)), .transform((map) => Object.values(map)),
]) ])
.catch([]) .catch([])

View file

@ -151,4 +151,50 @@ describe('util/schema', () => {
expect(called).toBeTrue(); 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,
});
});
});
}); });

View file

@ -143,3 +143,32 @@ export function looseRecord<T extends z.ZodTypeAny>(
return filteredRecord; 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;
}