From a68b49d8d0df9918ab333ba9679a0c2ed51fedd9 Mon Sep 17 00:00:00 2001
From: Sebastian Poxhofer <secustor@users.noreply.github.com>
Date: Sat, 20 Jan 2024 15:22:22 +0100
Subject: [PATCH] refactor(util/yaml): allow to set failure behavior when
 parsing multidoc yamls (#26763)

Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
---
 lib/util/yaml.spec.ts | 50 +++++++++++++++++++++++++++++++++++++++++++
 lib/util/yaml.ts      | 31 +++++++++++++++++++++------
 2 files changed, 75 insertions(+), 6 deletions(-)

diff --git a/lib/util/yaml.spec.ts b/lib/util/yaml.spec.ts
index d1768ad9ad..a79d96526a 100644
--- a/lib/util/yaml.spec.ts
+++ b/lib/util/yaml.spec.ts
@@ -122,6 +122,56 @@ describe('util/yaml', () => {
       ).toThrow();
     });
 
+    it('should throw if schema does not match and failureBehaviour "throw"', () => {
+      expect(() =>
+        parseYaml(
+          codeBlock`
+      myObject:
+        aString: foo
+      ---
+      aString: bar
+      `,
+          null,
+          {
+            customSchema: z.object({
+              myObject: z.object({
+                aString: z.string(),
+              }),
+            }),
+            failureBehaviour: 'throw',
+          },
+        ),
+      ).toThrow();
+    });
+
+    it('should still return valid elements if schema does not match with "filter" behaviour', () => {
+      expect(
+        parseYaml(
+          codeBlock`
+      myObject:
+        aString: foo
+      ---
+      aString: bar
+      `,
+          null,
+          {
+            customSchema: z.object({
+              myObject: z.object({
+                aString: z.string(),
+              }),
+            }),
+            failureBehaviour: 'filter',
+          },
+        ),
+      ).toEqual([
+        {
+          myObject: {
+            aString: 'foo',
+          },
+        },
+      ]);
+    });
+
     it('should parse content with templates', () => {
       expect(
         parseYaml(
diff --git a/lib/util/yaml.ts b/lib/util/yaml.ts
index 47d7a65474..43e8c05a39 100644
--- a/lib/util/yaml.ts
+++ b/lib/util/yaml.ts
@@ -6,20 +6,28 @@ import {
   dump as upstreamDump,
 } from 'js-yaml';
 import type { ZodType } from 'zod';
+import { logger } from '../logger';
 import { regEx } from './regex';
 
-type YamlOptions<
+interface YamlOptions<
   ResT = unknown,
   Schema extends ZodType<ResT> = ZodType<ResT>,
-> = {
+> extends LoadOptions {
   customSchema?: Schema;
   removeTemplates?: boolean;
-} & LoadOptions;
+}
+
+interface YamlOptionsMultiple<
+  ResT = unknown,
+  Schema extends ZodType<ResT> = ZodType<ResT>,
+> extends YamlOptions<ResT, Schema> {
+  failureBehaviour?: 'throw' | 'filter';
+}
 
 export function parseYaml<ResT = unknown>(
   content: string,
   iterator?: null | undefined,
-  options?: YamlOptions<ResT>,
+  options?: YamlOptionsMultiple<ResT>,
 ): ResT[] {
   const massagedContent = massageContent(content, options);
 
@@ -32,8 +40,19 @@ export function parseYaml<ResT = unknown>(
 
   const parsed: ResT[] = [];
   for (const element of rawDocuments) {
-    const singleParsed = schema.parse(element);
-    parsed.push(singleParsed);
+    const result = schema.safeParse(element);
+    if (result.success) {
+      parsed.push(result.data);
+      continue;
+    }
+
+    if (options?.failureBehaviour !== 'filter') {
+      throw new Error('Failed to parse YAML file', { cause: result.error });
+    }
+    logger.debug(
+      { error: result.error, document: element },
+      'Failed to parse schema for YAML',
+    );
   }
   return parsed;
 }
-- 
GitLab