« get me outta code hell

yaml: track skipped fields separately & report summary at bottom - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
author(quasar) nebula <qznebula@protonmail.com>2023-10-18 14:54:38 -0300
committer(quasar) nebula <qznebula@protonmail.com>2023-10-18 14:54:38 -0300
commit84d22c117d6deabd53aaee1546e3a99f5d6049c7 (patch)
tree0d89213f185aa89f88068d6cf9696023c998346e /src
parentea1b8196ba240d2cc4c64a9079947028cb536bf8 (diff)
yaml: track skipped fields separately & report summary at bottom
Diffstat (limited to 'src')
-rw-r--r--src/data/yaml.js80
1 files changed, 64 insertions, 16 deletions
diff --git a/src/data/yaml.js b/src/data/yaml.js
index 06ef5546..f49f48dd 100644
--- a/src/data/yaml.js
+++ b/src/data/yaml.js
@@ -171,12 +171,18 @@ function makeProcessDocument(
     const documentEntries = Object.entries(document)
       .filter(([field]) => !ignoredFields.includes(field));
 
+    const skippedFields = new Set();
+
     const unknownFields = documentEntries
       .map(([field]) => field)
       .filter((field) => !knownFields.includes(field));
 
     if (!empty(unknownFields)) {
       aggregate.push(new UnknownFieldsError(unknownFields));
+
+      for (const field of unknownFields) {
+        skippedFields.add(field);
+      }
     }
 
     const presentFields = Object.keys(document);
@@ -187,10 +193,17 @@ function makeProcessDocument(
       const fieldsPresent = presentFields.filter(field => fields.includes(field));
 
       if (fieldsPresent.length >= 2) {
-        fieldCombinationErrors.push(
-          new FieldCombinationError(
-            filterProperties(document, fieldsPresent),
-            message));
+        const filteredDocument =
+          filterProperties(
+            document,
+            fieldsPresent,
+            {preserveOriginalOrder: true});
+
+        fieldCombinationErrors.push(new FieldCombinationError(filteredDocument, message));
+
+        for (const field of Object.keys(filteredDocument)) {
+          skippedFields.add(field);
+        }
       }
     }
 
@@ -201,6 +214,7 @@ function makeProcessDocument(
     const fieldValues = {};
 
     for (const [field, value] of documentEntries) {
+      if (skippedFields.has(field)) continue;
       if (Object.hasOwn(fieldTransformations, field)) {
         fieldValues[field] = fieldTransformations[field](value);
       } else {
@@ -211,10 +225,8 @@ function makeProcessDocument(
     const sourceProperties = {};
 
     for (const [field, value] of Object.entries(fieldValues)) {
-      if (Object.hasOwn(fieldPropertyMapping, field)) {
-        const property = fieldPropertyMapping[field];
-        sourceProperties[property] = value;
-      }
+      const property = fieldPropertyMapping[field];
+      sourceProperties[property] = value;
     }
 
     const thing = Reflect.construct(thingConstructor, []);
@@ -227,6 +239,7 @@ function makeProcessDocument(
       try {
         thing[property] = value;
       } catch (caughtError) {
+        skippedFields.add(field);
         fieldValueErrors.push(new FieldValueError(field, property, value, caughtError));
       }
     }
@@ -235,6 +248,15 @@ function makeProcessDocument(
       aggregate.push(new FieldValueAggregateError(thingConstructor, fieldValueErrors));
     }
 
+    if (skippedFields.size >= 1) {
+      aggregate.push(
+        new SkippedFieldsSummaryError(
+          filterProperties(
+            document,
+            Array.from(skippedFields),
+            {preserveOriginalOrder: true})));
+    }
+
     return {thing, aggregate};
   });
 
@@ -248,30 +270,37 @@ function makeProcessDocument(
 
 export class UnknownFieldsError extends Error {
   constructor(fields) {
-    super(`Unknown fields present: ${fields.map(field => colors.red(field)).join(', ')}`);
+    super(`Unknown fields ignored: ${fields.map(field => colors.red(field)).join(', ')}`);
     this.fields = fields;
   }
 }
 
 export class FieldCombinationAggregateError extends AggregateError {
   constructor(errors) {
-    super(errors, `Errors in combinations of fields present`);
+    super(errors, `Invalid field combinations - all involved fields ignored`);
   }
 }
 
 export class FieldCombinationError extends Error {
   constructor(fields, message) {
     const fieldNames = Object.keys(fields);
-    const combinePart = `Don't combine ${fieldNames.map(field => colors.red(field)).join(', ')}`;
 
-    const messagePart =
+    const mainMessage = `Don't combine ${fieldNames.map(field => colors.red(field)).join(', ')}`;
+
+    const causeMessage =
       (typeof message === 'function'
-        ? `: ${message(fields)}`
+        ? message(fields)
      : typeof message === 'string'
-        ? `: ${message}`
-        : ``);
+        ? message
+        : null);
+
+    super(mainMessage, {
+      cause:
+        (causeMessage
+          ? new Error(causeMessage)
+          : null),
+    });
 
-    super(combinePart + messagePart);
     this.fields = fields;
   }
 }
@@ -295,6 +324,25 @@ export class FieldValueError extends Error {
   }
 }
 
+export class SkippedFieldsSummaryError extends Error {
+  constructor(filteredDocument) {
+    const entries = Object.entries(filteredDocument);
+
+    const lines =
+      entries.map(([field, value]) =>
+        ` - ${field}: ` +
+        inspect(value)
+          .split('\n')
+          .map((line, index) => index === 0 ? line : `   ${line}`)
+          .join('\n'));
+
+    super(
+      colors.bright(colors.yellow(`Altogether, skipped ${entries.length === 1 ? `1 field` : `${entries.length} fields`}:\n`)) +
+      lines.join('\n') + '\n' +
+      colors.bright(colors.yellow(`See above errors for details.`)));
+  }
+}
+
 export const processAlbumDocument = makeProcessDocument(T.Album, {
   fieldTransformations: {
     'Artists': parseContributors,