diff --git a/lib/util/schema-utils.spec.ts b/lib/util/schema-utils.spec.ts index 2f3c5581206d78..23cbddbd46122f 100644 --- a/lib/util/schema-utils.spec.ts +++ b/lib/util/schema-utils.spec.ts @@ -5,6 +5,7 @@ import { Json5, LooseArray, LooseRecord, + MultidocYaml, Toml, UtcDate, Yaml, @@ -324,6 +325,58 @@ describe('util/schema-utils', () => { }); }); + describe('MultidocYaml', () => { + const Schema = MultidocYaml.pipe( + z.array( + z.object({ + foo: z.number(), + }) + ) + ); + + it('parses valid yaml', () => { + expect( + Schema.parse(codeBlock` + foo: 111 + --- + foo: 222 + `) + ).toEqual([{ foo: 111 }, { foo: 222 }]); + }); + + it('throws error for non-string', () => { + expect(Schema.safeParse(42)).toMatchObject({ + error: { + issues: [ + { + message: 'Expected string, received number', + code: 'invalid_type', + expected: 'string', + received: 'number', + path: [], + }, + ], + }, + success: false, + }); + }); + + it('throws error for invalid yaml', () => { + expect(Schema.safeParse('clearly: "invalid" "yaml"')).toMatchObject({ + error: { + issues: [ + { + message: 'Invalid YAML', + code: 'custom', + path: [], + }, + ], + }, + success: false, + }); + }); + }); + describe('Toml', () => { const Schema = Toml.pipe( z.object({ foo: z.object({ bar: z.literal('baz') }) }) diff --git a/lib/util/schema-utils.ts b/lib/util/schema-utils.ts index 96690106e958e4..2394d80b415034 100644 --- a/lib/util/schema-utils.ts +++ b/lib/util/schema-utils.ts @@ -1,8 +1,8 @@ import { JsonMap, parse } from '@iarna/toml'; -import { load } from 'js-yaml'; +import { load, loadAll } from 'js-yaml'; import JSON5 from 'json5'; import { DateTime } from 'luxon'; -import type { JsonValue } from 'type-fest'; +import type { JsonArray, JsonValue } from 'type-fest'; import { z } from 'zod'; interface ErrorContext { @@ -235,6 +235,15 @@ export const Yaml = z.string().transform((str, ctx): JsonValue => { } }); +export const MultidocYaml = z.string().transform((str, ctx): JsonArray => { + try { + return loadAll(str, null, { json: true }) as JsonArray; + } catch (e) { + ctx.addIssue({ code: 'custom', message: 'Invalid YAML' }); + return z.NEVER; + } +}); + export const Toml = z.string().transform((str, ctx): JsonMap => { try { return parse(str);