Skip to content

Commit 277a4b4

Browse files
authored
feat: include more information in exception (introduce new type PatchingException which wraps the original exception) (#108)
1 parent e12a48c commit 277a4b4

File tree

4 files changed

+92
-7
lines changed

4 files changed

+92
-7
lines changed

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ ChronoJsonDiffPatch/ChronoJsonDiffPatch/bin/Debug/ChronoJsonDiffPatch.xml
1717

1818
*.exe
1919

20-
*.xml
20+
*.xml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
using Newtonsoft.Json.Linq;
2+
3+
namespace ChronoJsonDiffPatch;
4+
5+
/// <summary>
6+
/// is thrown when something fails to be patched
7+
/// </summary>
8+
/// <remarks>
9+
/// it's basically a wrapper around the inner exception with some helpful debugging information
10+
/// </remarks>
11+
public class PatchingException<TEntity> : Exception
12+
{
13+
/// <summary>
14+
/// state of the entity at +/- infinity (before any patch was applied)
15+
/// </summary>
16+
public TEntity StateOfEntityBeforeAnyPatch { get; }
17+
18+
/// <summary>
19+
/// state of the entity at the point where Applying <see cref="FailedPatch"/> failed
20+
/// </summary>
21+
public JToken StateOfEntityBeforePatch { get; }
22+
23+
/// <summary>
24+
/// patch that could not be applied
25+
/// </summary>
26+
public JToken? FailedPatch { get; }
27+
28+
/// <summary>
29+
/// index of the patch inside the chain
30+
/// </summary>
31+
public int Index { get; }
32+
33+
/// <summary>
34+
/// instantiate with all the necessary information
35+
/// </summary>
36+
/// <param name="stateOfEntityBeforeAnyPatch"></param>
37+
/// <param name="left"></param>
38+
/// <param name="patch"></param>
39+
/// <param name="index"></param>
40+
/// <param name="message"></param>
41+
/// <param name="innerException"></param>
42+
public PatchingException(
43+
TEntity stateOfEntityBeforeAnyPatch,
44+
JToken left,
45+
JToken? patch,
46+
int index,
47+
string message,
48+
Exception innerException
49+
)
50+
: base(message, innerException)
51+
{
52+
StateOfEntityBeforeAnyPatch = stateOfEntityBeforeAnyPatch;
53+
StateOfEntityBeforePatch = left;
54+
FailedPatch = patch;
55+
Index = index;
56+
}
57+
58+
/// <inheritdoc />
59+
public override string Message =>
60+
$"{base.Message}. Patch (index {Index}): {FailedPatch ?? "<null>"}; State: {StateOfEntityBeforePatch}";
61+
}

ChronoJsonDiffPatch/ChronoJsonDiffPatch/TimeRangePatchChain.cs

+25-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.Diagnostics.Contracts;
1+
using System.Diagnostics.Contracts;
22
using Itenso.TimePeriod;
33
using JsonDiffPatchDotNet;
44
using Newtonsoft.Json;
@@ -559,6 +559,8 @@ public TEntity PatchToDate(TEntity initialEntity, DateTimeOffset keyDate)
559559
{
560560
case PatchingDirection.ParallelWithTime:
561561
{
562+
var index = -1;
563+
562564
foreach (
563565
var existingPatch in GetAll()
564566
.Where(p =>
@@ -573,16 +575,18 @@ var existingPatch in GetAll()
573575
)
574576
)
575577
{
578+
index += 1;
576579
var jtokenPatch = JsonConvert.DeserializeObject<JToken>(
577580
existingPatch.Patch!.RootElement.GetRawText()
578581
);
579582
try
580583
{
581584
left = jdp.Patch(left, jtokenPatch);
582585
}
583-
catch (Exception exc) when (_skipConditions?.Any() == true)
586+
catch (Exception exc)
584587
{
585588
var entityBeforePatch = _deserialize(left.ToString());
589+
586590
if (
587591
_skipConditions?.Any(sc =>
588592
sc.ShouldSkipPatch(entityBeforePatch, existingPatch, exc)
@@ -593,14 +597,22 @@ var existingPatch in GetAll()
593597
continue;
594598
}
595599

596-
throw; // re-throw
600+
throw new PatchingException<TEntity>(
601+
stateOfEntityBeforeAnyPatch: initialEntity,
602+
left: left,
603+
patch: jtokenPatch,
604+
index: index,
605+
message: $"Failed to apply patches ({PatchingDirection}): {exc.Message}; None of the {_skipConditions?.Count() ?? 0} skip conditions applied",
606+
innerException: exc
607+
);
597608
}
598609
}
599610

600611
return _deserialize(JsonConvert.SerializeObject(left));
601612
}
602613
case PatchingDirection.AntiParallelWithTime:
603614
{
615+
var index = 0;
604616
foreach (
605617
var existingPatch in GetAll()
606618
.Where(p => p.End > keyDate)
@@ -610,14 +622,15 @@ var existingPatch in GetAll()
610622
)
611623
)
612624
{
625+
index += 1;
613626
var jtokenPatch = JsonConvert.DeserializeObject<JToken>(
614627
existingPatch.Patch!.RootElement.GetRawText()
615628
);
616629
try
617630
{
618631
left = jdp.Unpatch(left, jtokenPatch);
619632
}
620-
catch (Exception exc) when (_skipConditions?.Any() == true)
633+
catch (Exception exc)
621634
{
622635
var entityBeforePatch = _deserialize(left.ToString());
623636
if (
@@ -630,7 +643,14 @@ var existingPatch in GetAll()
630643
continue;
631644
}
632645

633-
throw; // re-throw
646+
throw new PatchingException<TEntity>(
647+
stateOfEntityBeforeAnyPatch: initialEntity,
648+
left: left,
649+
patch: jtokenPatch,
650+
index: index,
651+
message: $"Failed to apply patches ({PatchingDirection}): {exc.Message}; None of the {_skipConditions?.Count() ?? 0} skip conditions applied",
652+
innerException: exc
653+
);
634654
}
635655
}
636656

ChronoJsonDiffPatch/ChronoJsonDiffPatchTests/ListPatchingTests.cs

+5-1
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,11 @@ public void Reproduce_ArgumentOutOfRangeException()
127127
antiparallelChain.PatchToDate(corruptedInitialEntity, keyDate1 - TimeSpan.FromDays(10));
128128
applyingPatchesToACorruptedInitialEntity
129129
.Should()
130-
.ThrowExactly<ArgumentOutOfRangeException>();
130+
.ThrowExactly<PatchingException<EntityWithList>>()
131+
.Which.InnerException.Should()
132+
.NotBeNull()
133+
.And.Subject.Should()
134+
.BeOfType<ArgumentOutOfRangeException>();
131135
antiparallelChain.PatchesHaveBeenSkipped.Should().BeFalse();
132136
}
133137

0 commit comments

Comments
 (0)