From 271a7b51885e1859412215d56fe040c4f4f88f2c Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 12 Oct 2026 14:27:46 +0000 Subject: [PATCH 1/2] Report a malformed JSON import instead of crashing [patch] ImportJson caught nothing, so a truncated file, an object instead of an array, or a field of the wrong type ended the process with a stack trace (and ended the interactive menu with it). It now catches JsonException and the ArgumentException a semantic type throws on validation, prints an error naming the file, and returns null so Run fails before anything is merged or saved, the way the CSV path already skips bad rows. Fixes ktsu-dev/ImageDescriber#172 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01EizTraGREbkNoiWDb3o9eC --- ImageDescriber.Test/ImportTests.cs | 31 ++++++++++++++++++++++++++++++ ImageDescriber/Verbs/Import.cs | 20 +++++++++++++++++-- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/ImageDescriber.Test/ImportTests.cs b/ImageDescriber.Test/ImportTests.cs index aa93966..1bb91d2 100644 --- a/ImageDescriber.Test/ImportTests.cs +++ b/ImageDescriber.Test/ImportTests.cs @@ -288,4 +288,35 @@ public void ParseCsvRecordsKeepsLineBreaksInsideQuotedFields() Assert.AreSequenceEqual(ExpectedEscapedQuoteRecord, records[2].Fields); Assert.AreEqual(5, records[2].LineNumber); } + + [TestMethod] + [DataRow("{\"not\":\"array\"}")] + [DataRow("[{\"Hash\":\"aaaa")] + [DataRow("[{\"Hash\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"FileSizeBytes\":\"abc\"}]")] + public void RunReportsAMalformedJsonFileWithoutThrowingOrChangingTheStore(string json) + { + PersistentState originalSettings = Program.Settings; + TextWriter originalOut = Console.Out; + string file = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N") + ".json"); + File.WriteAllText(file, json); + try + { + Program.Settings = new PersistentState(); + using StringWriter output = new(); + Console.SetOut(output); + + Import import = new(); + import.Run(new Import { InputPath = file }); + + Assert.IsTrue(import.Failed); + Assert.IsEmpty(Program.Settings.Descriptions); + Assert.Contains(file, output.ToString()); + } + finally + { + Console.SetOut(originalOut); + Program.Settings = originalSettings; + File.Delete(file); + } + } } diff --git a/ImageDescriber/Verbs/Import.cs b/ImageDescriber/Verbs/Import.cs index af94f88..522d12f 100644 --- a/ImageDescriber/Verbs/Import.cs +++ b/ImageDescriber/Verbs/Import.cs @@ -162,10 +162,26 @@ internal static bool MergeKnownPaths(ImageDescription source, ImageDescription t return pathsAdded; } - private static List ImportJson(AbsoluteFilePath inputPath) + private static List? ImportJson(AbsoluteFilePath inputPath) { string json = File.ReadAllText(inputPath.WeakString); - return JsonSerializer.Deserialize>(json, JsonOptions) ?? []; + + // A truncated or hand-edited file, or one that isn't an export at all, has to end the + // import before anything is merged or saved, not end the process with a stack trace. + try + { + return JsonSerializer.Deserialize>(json, JsonOptions) ?? []; + } + catch (JsonException ex) + { + Console.WriteLine($"Error: {inputPath} is not a valid ImageDescriber JSON export: {ex.Message}"); + return null; + } + catch (ArgumentException ex) + { + Console.WriteLine($"Error: {inputPath} is not a valid ImageDescriber JSON export: {ex.Message}"); + return null; + } } private static List ImportCsv(AbsoluteFilePath inputPath) => From 14cf1fc41216a35b4a0214bc3ef72439ba541450 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 12 Oct 2026 14:36:18 +0000 Subject: [PATCH 2/2] Cover the semantic-validation failure on JSON import A relative KnownPaths entry makes the AbsoluteFilePath conversion throw ArgumentException, which is the second catch in ImportJson; the test now exercises it alongside the three JsonException cases. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01EizTraGREbkNoiWDb3o9eC --- ImageDescriber.Test/ImportTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/ImageDescriber.Test/ImportTests.cs b/ImageDescriber.Test/ImportTests.cs index 1bb91d2..402f17a 100644 --- a/ImageDescriber.Test/ImportTests.cs +++ b/ImageDescriber.Test/ImportTests.cs @@ -293,6 +293,7 @@ public void ParseCsvRecordsKeepsLineBreaksInsideQuotedFields() [DataRow("{\"not\":\"array\"}")] [DataRow("[{\"Hash\":\"aaaa")] [DataRow("[{\"Hash\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"FileSizeBytes\":\"abc\"}]")] + [DataRow("[{\"Hash\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"KnownPaths\":[\"relative/photo.jpg\"]}]")] public void RunReportsAMalformedJsonFileWithoutThrowingOrChangingTheStore(string json) { PersistentState originalSettings = Program.Settings;