From b82d56b6cac3b3fc712620db072cc6bccfaab19e Mon Sep 17 00:00:00 2001 From: Tim Haasdyk Date: Tue, 10 Dec 2024 14:45:11 +0100 Subject: [PATCH] Refactor collection diffing to use class/object for add, delete, move & replace --- .../MiniLcm.Tests/DiffCollectionTests.cs | 64 +++--- .../SyncHelpers/ComplexFormTypeSync.cs | 35 +-- .../MiniLcm/SyncHelpers/DiffCollection.cs | 157 +++++--------- .../FwLite/MiniLcm/SyncHelpers/EntrySync.cs | 200 +++++++++++------- .../SyncHelpers/ExampleSentenceSync.cs | 39 ++-- .../MiniLcm/SyncHelpers/PartOfSpeechSync.cs | 35 +-- .../MiniLcm/SyncHelpers/SemanticDomainSync.cs | 35 +-- .../FwLite/MiniLcm/SyncHelpers/SenseSync.cs | 38 ++-- .../MiniLcm/SyncHelpers/WritingSystemSync.cs | 45 ++-- 9 files changed, 335 insertions(+), 313 deletions(-) diff --git a/backend/FwLite/MiniLcm.Tests/DiffCollectionTests.cs b/backend/FwLite/MiniLcm.Tests/DiffCollectionTests.cs index 5f72cbce6..a24887c96 100644 --- a/backend/FwLite/MiniLcm.Tests/DiffCollectionTests.cs +++ b/backend/FwLite/MiniLcm.Tests/DiffCollectionTests.cs @@ -11,7 +11,7 @@ public async Task MatchingCollections_NoChangesAreGenerated() { var value1 = Orderable(1, Guid.NewGuid()); var value2 = Orderable(2, Guid.NewGuid()); - var (changeCount, _, _) = await Diff([value1, value2], [value1, value2]); + var (changeCount, _) = await Diff([value1, value2], [value1, value2]); changeCount.Should().Be(0); } @@ -22,17 +22,17 @@ public async Task AddedValues() var value1 = Orderable(1, Guid.NewGuid()); var value2 = Orderable(2, Guid.NewGuid()); var value3 = Orderable(3, Guid.NewGuid()); - var (changeCount, diffApi, api) = await Diff([value1], [value2, value1, value3]); + var (changeCount, diffApi) = await Diff([value1], [value2, value1, value3]); changeCount.Should().Be(2); var x_v1 = Between(next: value1); - diffApi.Verify(dApi => dApi.Add(api, value2, x_v1), Times.Once); + diffApi.Verify(dApi => dApi.Add(value2, x_v1), Times.Once); var v1_x = Between(previous: value1); - diffApi.Verify(dApi => dApi.Add(api, value3, v1_x), Times.Once); + diffApi.Verify(dApi => dApi.Add(value3, v1_x), Times.Once); - diffApi.Verify(dApi => dApi.Replace(api, value1, value1), Times.Once); + diffApi.Verify(dApi => dApi.Replace(value1, value1), Times.Once); diffApi.VerifyNoOtherCalls(); } @@ -42,12 +42,12 @@ public async Task RemovedValues() var value1 = Orderable(1, Guid.NewGuid()); var value2 = Orderable(2, Guid.NewGuid()); var value3 = Orderable(3, Guid.NewGuid()); - var (changeCount, diffApi, api) = await Diff([value2, value1, value3], [value1]); + var (changeCount, diffApi) = await Diff([value2, value1, value3], [value1]); changeCount.Should().Be(2); - diffApi.Verify(dApi => dApi.Remove(api, value2), Times.Once); - diffApi.Verify(dApi => dApi.Remove(api, value3), Times.Once); - diffApi.Verify(dApi => dApi.Replace(api, value1, value1), Times.Once); + diffApi.Verify(dApi => dApi.Remove(value2), Times.Once); + diffApi.Verify(dApi => dApi.Remove(value3), Times.Once); + diffApi.Verify(dApi => dApi.Replace(value1, value1), Times.Once); diffApi.VerifyNoOtherCalls(); } @@ -56,13 +56,13 @@ public async Task SwappedValues() { var value1 = Orderable(1, Guid.NewGuid()); var value2 = Orderable(2, Guid.NewGuid()); - var (changeCount, diffApi, api) = await Diff([value1, value2], [value2, value1]); + var (changeCount, diffApi) = await Diff([value1, value2], [value2, value1]); changeCount.Should().Be(1); var v2_x = Between(previous: value2); - diffApi.Verify(dApi => dApi.Move(api, value1, v2_x), Times.Once); - diffApi.Verify(dApi => dApi.Replace(api, value1, value1), Times.Once); - diffApi.Verify(dApi => dApi.Replace(api, value2, value2), Times.Once); + diffApi.Verify(dApi => dApi.Move(value1, v2_x), Times.Once); + diffApi.Verify(dApi => dApi.Replace(value1, value1), Times.Once); + diffApi.Verify(dApi => dApi.Replace(value2, value2), Times.Once); diffApi.VerifyNoOtherCalls(); } @@ -148,14 +148,14 @@ public async Task DiffTests(CollectionDiffTestCase testCase) } } - var (changeCount, diffApi, api) = await Diff(testCase.OldValues, testCase.NewValues); + var (changeCount, diffApi) = await Diff(testCase.OldValues, testCase.NewValues); using var scope = new AssertionScope(); changeCount.Should().Be(testCase.ExpectedOperations.Count); var expectedReplaceCount = testCase.OldValues.Select(v => v.Id).Intersect(testCase.NewValues.Select(v => v.Id)).Count(); - diffApi.Verify(dApi => dApi.Replace(api, It.IsAny(), It.IsAny()), Times.Exactly(expectedReplaceCount)); + diffApi.Verify(dApi => dApi.Replace(It.IsAny(), It.IsAny()), Times.Exactly(expectedReplaceCount)); foreach (var operation in testCase.ExpectedOperations) { @@ -167,7 +167,6 @@ public async Task DiffTests(CollectionDiffTestCase testCase) var movedValue = testCase.OldValues[operation.From.Value]; diffApi.Verify( dApi => dApi.Move( - api, movedValue, operation.Between ), @@ -179,7 +178,6 @@ public async Task DiffTests(CollectionDiffTestCase testCase) var removedValue = testCase.OldValues[operation.From.Value]; diffApi.Verify( dApi => dApi.Remove( - api, removedValue ), Times.Once @@ -191,7 +189,6 @@ public async Task DiffTests(CollectionDiffTestCase testCase) var addedValue = testCase.NewValues[operation.To.Value]; diffApi.Verify( dApi => dApi.Add( - api, addedValue, operation.Between ), @@ -231,17 +228,16 @@ private static BetweenPosition Between(Guid? previous = null, Guid? next = null) }; } - private static async Task<(int, Mock, IMiniLcmApi)> Diff(IOrderable[] oldValues, IOrderable[] newValues) + private static async Task<(int, Mock>)> Diff(IOrderable[] oldValues, IOrderable[] newValues) { - var api = new Mock().Object; - var diffApi = new Mock(); - diffApi.Setup(f => f.Add(api, It.IsAny(), It.IsAny())) + var diffApi = new Mock>(); + diffApi.Setup(f => f.Add(It.IsAny(), It.IsAny())) .ReturnsAsync(1); - diffApi.Setup(f => f.Remove(api, It.IsAny())) + diffApi.Setup(f => f.Remove(It.IsAny())) .ReturnsAsync(1); - diffApi.Setup(f => f.Move(api, It.IsAny(), It.IsAny())) + diffApi.Setup(f => f.Move(It.IsAny(), It.IsAny())) .ReturnsAsync(1); - diffApi.Setup(f => f.Replace(api, It.IsAny(), It.IsAny())) + diffApi.Setup(f => f.Replace(It.IsAny(), It.IsAny())) .Returns((IMiniLcmApi api, IOrderable oldValue, IOrderable newValue) => { try @@ -255,31 +251,19 @@ private static BetweenPosition Between(Guid? previous = null, Guid? next = null) } }); - var changeCount = await DiffCollection.DiffOrderable(api, oldValues, newValues, - diffApi.Object.Add, - diffApi.Object.Remove, - diffApi.Object.Move, - diffApi.Object.Replace); + var changeCount = await DiffCollection.DiffOrderable(oldValues, newValues, diffApi.Object); - return (changeCount, diffApi, api); + return (changeCount, diffApi); } } public class DiffResult { public required int ChangeCount { get; init; } - public required Mock DiffApi { get; init; } + public required Mock> DiffApi { get; init; } public required IMiniLcmApi Api { get; init; } } -public interface DiffApi -{ - Task Add(IMiniLcmApi api, IOrderable value, BetweenPosition? between); - Task Remove(IMiniLcmApi api, IOrderable value); - Task Move(IMiniLcmApi api, IOrderable value, BetweenPosition? between); - Task Replace(IMiniLcmApi api, IOrderable oldValue, IOrderable newValue); -} - public class CollectionDiffTestCase { public required IOrderable[] OldValues { get; init; } diff --git a/backend/FwLite/MiniLcm/SyncHelpers/ComplexFormTypeSync.cs b/backend/FwLite/MiniLcm/SyncHelpers/ComplexFormTypeSync.cs index e7ae771cf..05a92442f 100644 --- a/backend/FwLite/MiniLcm/SyncHelpers/ComplexFormTypeSync.cs +++ b/backend/FwLite/MiniLcm/SyncHelpers/ComplexFormTypeSync.cs @@ -9,21 +9,10 @@ public static async Task Sync(ComplexFormType[] afterComplexFormTypes, ComplexFormType[] beforeComplexFormTypes, IMiniLcmApi api) { - return await DiffCollection.Diff(api, + return await DiffCollection.Diff( beforeComplexFormTypes, afterComplexFormTypes, - complexFormType => complexFormType.Id, - static async (api, afterComplexFormType) => - { - await api.CreateComplexFormType(afterComplexFormType); - return 1; - }, - static async (api, beforeComplexFormType) => - { - await api.DeleteComplexFormType(beforeComplexFormType.Id); - return 1; - }, - static (api, beforeComplexFormType, afterComplexFormType) => Sync(beforeComplexFormType, afterComplexFormType, api)); + new ComplexFormTypesDiffApi(api)); } public static async Task Sync(ComplexFormType before, @@ -44,4 +33,24 @@ public static async Task Sync(ComplexFormType before, if (patchDocument.Operations.Count == 0) return null; return new UpdateObjectInput(patchDocument); } + + private class ComplexFormTypesDiffApi(IMiniLcmApi api) : ObjectWithIdCollectionDiffApi + { + public override async Task Add(ComplexFormType afterComplexFormType) + { + await api.CreateComplexFormType(afterComplexFormType); + return 1; + } + + public override async Task Remove(ComplexFormType beforeComplexFormType) + { + await api.DeleteComplexFormType(beforeComplexFormType.Id); + return 1; + } + + public override Task Replace(ComplexFormType beforeComplexFormType, ComplexFormType afterComplexFormType) + { + return Sync(beforeComplexFormType, afterComplexFormType, api); + } + } } diff --git a/backend/FwLite/MiniLcm/SyncHelpers/DiffCollection.cs b/backend/FwLite/MiniLcm/SyncHelpers/DiffCollection.cs index 289b9b587..e75dfe768 100644 --- a/backend/FwLite/MiniLcm/SyncHelpers/DiffCollection.cs +++ b/backend/FwLite/MiniLcm/SyncHelpers/DiffCollection.cs @@ -4,129 +4,111 @@ namespace MiniLcm.SyncHelpers; +public abstract class CollectionDiffApi where TId : notnull +{ + public abstract Task Add(T value); + public virtual async Task<(int, T)> AddAndGet(T value) + { + var changes = await Add(value); + return (changes, value); + } + public abstract Task Remove(T value); + public abstract Task Replace(T before, T after); + public abstract TId GetId(T value); +} + +public abstract class ObjectWithIdCollectionDiffApi : CollectionDiffApi where T : IObjectWithId +{ + public override Guid GetId(T value) + { + return value.Id; + } +} + +public interface OrderableCollectionDiffApi where T : IOrderable +{ + Task Add(T value, BetweenPosition between); + Task Remove(T value); + Task Move(T value, BetweenPosition between); + Task Replace(T before, T after); +} + public static class DiffCollection { /// /// Diffs a list, for new items calls add, it will then call update for the item returned from the add, using that as the before item for the replace call /// - /// - /// - /// - /// - /// api, value, return value to be used as the before item for the replace call - /// - /// api, before, after is the parameter order - /// - /// - /// public static async Task DiffAddThenUpdate( - IMiniLcmApi api, IList before, IList after, - Func identity, - Func> add, - Func> remove, - Func> replace) where TId : notnull + CollectionDiffApi diffApi) where TId : notnull { var changes = 0; - var afterEntriesDict = after.ToDictionary(identity); + var afterEntriesDict = after.ToDictionary(diffApi.GetId); foreach (var beforeEntry in before) { - if (afterEntriesDict.TryGetValue(identity(beforeEntry), out var afterEntry)) + if (afterEntriesDict.TryGetValue(diffApi.GetId(beforeEntry), out var afterEntry)) { - changes += await replace(api, beforeEntry, afterEntry); + changes += await diffApi.Replace(beforeEntry, afterEntry); } else { - changes += await remove(api, beforeEntry); + changes += await diffApi.Remove(beforeEntry); } - afterEntriesDict.Remove(identity(beforeEntry)); + afterEntriesDict.Remove(diffApi.GetId(beforeEntry)); } var postAddUpdates = new List<(T created, T after)>(afterEntriesDict.Values.Count); foreach (var value in afterEntriesDict.Values) { - changes++; - postAddUpdates.Add((await add(api, value), value)); + var (change, created) = await diffApi.AddAndGet(value); + changes += change; + postAddUpdates.Add((created, value)); } - foreach ((T createdItem, T afterItem) in postAddUpdates) + foreach ((var createdItem, var afterItem) in postAddUpdates) { //todo this may do a lot more work than it needs to, eg sense will be created during add, but they will be checked again here when we know they didn't change - await replace(api, createdItem, afterItem); + await diffApi.Replace(createdItem, afterItem); } return changes; } - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// api, before, after is the parameter order - /// - /// - /// public static async Task Diff( - IMiniLcmApi api, IList before, IList after, - Func identity, - Func> add, - Func> remove, - Func> replace) where TId : notnull + CollectionDiffApi diffApi) where TId : notnull { var changes = 0; - var afterEntriesDict = after.ToDictionary(identity); + var afterEntriesDict = after.ToDictionary(diffApi.GetId); foreach (var beforeEntry in before) { - if (afterEntriesDict.TryGetValue(identity(beforeEntry), out var afterEntry)) + if (afterEntriesDict.TryGetValue(diffApi.GetId(beforeEntry), out var afterEntry)) { - changes += await replace(api, beforeEntry, afterEntry); + changes += await diffApi.Replace(beforeEntry, afterEntry); } else { - changes += await remove(api, beforeEntry); + changes += await diffApi.Remove(beforeEntry); } - afterEntriesDict.Remove(identity(beforeEntry)); + afterEntriesDict.Remove(diffApi.GetId(beforeEntry)); } foreach (var value in afterEntriesDict.Values) { - changes += await add(api, value); + changes += await diffApi.Add(value); } return changes; } - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// api, before, after is the parameter order - /// - /// - /// public static async Task DiffOrderable( - IMiniLcmApi api, IList before, IList after, - Func> add, - Func> remove, - Func> move, - Func> replace) where T : IOrderable + OrderableCollectionDiffApi diffApi) where T : IOrderable { var positionDiffs = DiffPositions(before, after) // Order: Deletes first, then adds and moves from lowest to highest new index @@ -144,18 +126,18 @@ public static async Task DiffOrderable( { var afterEntry = after[diff.To.Value]; var between = GetStableBetween(diff.To.Value, after, stableIds); - changes += await move(api, afterEntry, between); + changes += await diffApi.Move(afterEntry, between); stableIds.Add(afterEntry.Id); } else if (diff.From is not null) { - changes += await remove(api, before[diff.From.Value]); + changes += await diffApi.Remove(before[diff.From.Value]); } else if (diff.To is not null) { var afterEntry = after[diff.To.Value]; var between = GetStableBetween(diff.To.Value, after, stableIds); - changes += await add(api, afterEntry, between); + changes += await diffApi.Add(afterEntry, between); stableIds.Add(afterEntry.Id); } } @@ -165,48 +147,13 @@ public static async Task DiffOrderable( { if (afterEntriesDict.TryGetValue(beforeEntry.Id, out var afterEntry)) { - changes += await replace(api, beforeEntry, afterEntry); + changes += await diffApi.Replace(beforeEntry, afterEntry); } } return changes; } - public static async Task Diff( - IMiniLcmApi api, - IList before, - IList after, - Func> add, - Func> remove, - Func> replace) where T : IObjectWithId - { - return await Diff(api, before, after, t => t.Id, add, remove, replace); - } - - public static async Task Diff( - IMiniLcmApi api, - IList before, - IList after, - Func add, - Func remove, - Func> replace) where T : IObjectWithId - { - return await Diff(api, - before, - after, - async (api, entry) => - { - await add(api, entry); - return 1; - }, - async (api, entry) => - { - await remove(api, entry); - return 1; - }, - async (api, beforeEntry, afterEntry) => await replace(api, beforeEntry, afterEntry)); - } - private static BetweenPosition GetStableBetween(int i, IList current, IReadOnlyList stable) where T : IOrderable { T? beforeEntity = default; diff --git a/backend/FwLite/MiniLcm/SyncHelpers/EntrySync.cs b/backend/FwLite/MiniLcm/SyncHelpers/EntrySync.cs index 1a0097873..aba125deb 100644 --- a/backend/FwLite/MiniLcm/SyncHelpers/EntrySync.cs +++ b/backend/FwLite/MiniLcm/SyncHelpers/EntrySync.cs @@ -10,21 +10,7 @@ public static async Task Sync(Entry[] afterEntries, Entry[] beforeEntries, IMiniLcmApi api) { - Func> add = static async (api, afterEntry) => - { - //create each entry without components. - //After each entry is created, then replace will be called to create those components - var entryWithoutEntryRefs = afterEntry.WithoutEntryRefs(); - await api.CreateEntry(entryWithoutEntryRefs); - return entryWithoutEntryRefs; - }; - Func> remove = static async (api, beforeEntry) => - { - await api.DeleteEntry(beforeEntry.Id); - return 1; - }; - Func> replace = static async (api, beforeEntry, afterEntry) => await Sync(afterEntry, beforeEntry, api); - return await DiffCollection.DiffAddThenUpdate(api, beforeEntries, afterEntries, entry => entry.Id, add, remove, replace); + return await DiffCollection.DiffAddThenUpdate(beforeEntries, afterEntries, new EntriesDiffApi(api)); } public static async Task Sync(Entry afterEntry, Entry beforeEntry, IMiniLcmApi api) @@ -51,61 +37,18 @@ private static async Task Sync(Guid entryId, IList beforeComplexFormTypes, IMiniLcmApi api) { - return await DiffCollection.Diff(api, + return await DiffCollection.Diff( beforeComplexFormTypes, afterComplexFormTypes, - complexFormType => complexFormType.Id, - async (api, afterComplexFormType) => - { - await api.AddComplexFormType(entryId, afterComplexFormType.Id); - return 1; - }, - async (api, beforeComplexFormType) => - { - await api.RemoveComplexFormType(entryId, beforeComplexFormType.Id); - return 1; - }, - //do nothing, complex form types are not editable, ignore any changes to them here - static (api, beforeComplexFormType, afterComplexFormType) => Task.FromResult(0)); + new ComplexFormTypesDiffApi(api, entryId)); } private static async Task Sync(IList afterComponents, IList beforeComponents, IMiniLcmApi api) { - return await DiffCollection.Diff(api, + return await DiffCollection.Diff( beforeComponents, afterComponents, - //we can't use the ID as there's none defined by Fw so it won't work as a sync key - component => (component.ComplexFormEntryId, component.ComponentEntryId, component.ComponentSenseId), - static async (api, afterComponent) => - { - //change id, since we're not using the id as the key for this collection - //the id may be the same, which is not what we want here - afterComponent.Id = Guid.NewGuid(); - try - { - await api.CreateComplexFormComponent(afterComponent); - } - catch (NotFoundException) - { - //this can happen if the entry was deleted, so we can just ignore it - } - return 1; - }, - static async (api, beforeComponent) => - { - await api.DeleteComplexFormComponent(beforeComponent); - return 1; - }, - static (api, beforeComponent, afterComponent) => - { - if (beforeComponent.ComplexFormEntryId == afterComponent.ComplexFormEntryId && - beforeComponent.ComponentEntryId == afterComponent.ComponentEntryId && - beforeComponent.ComponentSenseId == afterComponent.ComponentSenseId) - { - return Task.FromResult(0); - } - throw new InvalidOperationException($"changing complex form components is not supported, they should just be deleted and recreated"); - } + new ComplexFormComponentsDiffApi(api) ); } @@ -114,23 +57,7 @@ private static async Task SensesSync(Guid entryId, IList beforeSenses, IMiniLcmApi api) { - Func> add = async (api, sense, between) => - { - await api.CreateSense(entryId, sense, between); - return 1; - }; - Func> move = async (api, sense, between) => - { - await api.MoveSense(entryId, sense, between); - return 1; - }; - Func> remove = async (api, sense) => - { - await api.DeleteSense(entryId, sense.Id); - return 1; - }; - Func> replace = async (api, beforeSense, afterSense) => await SenseSync.Sync(entryId, afterSense, beforeSense, api); - return await DiffCollection.DiffOrderable(api, beforeSenses, afterSenses, add, remove, move, replace); + return await DiffCollection.DiffOrderable(beforeSenses, afterSenses, new SensesDiffApi(api, entryId)); } public static UpdateObjectInput? EntryDiffToUpdate(Entry beforeEntry, Entry afterEntry) @@ -143,4 +70,119 @@ private static async Task SensesSync(Guid entryId, if (patchDocument.Operations.Count == 0) return null; return new UpdateObjectInput(patchDocument); } + + private class EntriesDiffApi(IMiniLcmApi api) : ObjectWithIdCollectionDiffApi + { + public override async Task<(int, Entry)> AddAndGet(Entry afterEntry) + { + //create each entry without components. + //After each entry is created, then replace will be called to create those components + var entryWithoutEntryRefs = afterEntry.WithoutEntryRefs(); + var changes = await Add(entryWithoutEntryRefs); + return (changes, entryWithoutEntryRefs); + } + + public override async Task Add(Entry afterEntry) + { + await api.CreateEntry(afterEntry); + return 1; + } + + public override async Task Remove(Entry entry) + { + await api.DeleteEntry(entry.Id); + return 1; + } + + public override Task Replace(Entry before, Entry after) + { + return Sync(after, before, api); + } + } + + private class ComplexFormTypesDiffApi(IMiniLcmApi api, Guid entryId) : ObjectWithIdCollectionDiffApi + { + public override async Task Add(ComplexFormType afterComplexFormType) + { + await api.AddComplexFormType(entryId, afterComplexFormType.Id); + return 1; + } + + public override async Task Remove(ComplexFormType beforeComplexFormType) + { + await api.RemoveComplexFormType(entryId, beforeComplexFormType.Id); + return 1; + } + + public override Task Replace(ComplexFormType before, ComplexFormType after) + { + return Task.FromResult(0); + } + } + + private class ComplexFormComponentsDiffApi(IMiniLcmApi api) : CollectionDiffApi + { + public override (Guid, Guid, Guid?) GetId(ComplexFormComponent component) + { + //we can't use the ID as there's none defined by Fw so it won't work as a sync key + return (component.ComplexFormEntryId, component.ComponentEntryId, component.ComponentSenseId); + } + + public override async Task Add(ComplexFormComponent afterComplexFormType) + { + afterComplexFormType.Id = Guid.NewGuid(); + try + { + await api.CreateComplexFormComponent(afterComplexFormType); + } + catch (NotFoundException) + { + //this can happen if the entry was deleted, so we can just ignore it + } + return 1; + } + + public override async Task Remove(ComplexFormComponent beforeComplexFormType) + { + await api.DeleteComplexFormComponent(beforeComplexFormType); + return 1; + } + + public override Task Replace(ComplexFormComponent beforeComponent, ComplexFormComponent afterComponent) + { + if (beforeComponent.ComplexFormEntryId == afterComponent.ComplexFormEntryId && + beforeComponent.ComponentEntryId == afterComponent.ComponentEntryId && + beforeComponent.ComponentSenseId == afterComponent.ComponentSenseId) + { + return Task.FromResult(0); + } + throw new InvalidOperationException($"changing complex form components is not supported, they should just be deleted and recreated"); + } + } + + private class SensesDiffApi(IMiniLcmApi api, Guid entryId) : OrderableCollectionDiffApi + { + public async Task Add(Sense sense, BetweenPosition between) + { + await api.CreateSense(entryId, sense, between); + return 1; + } + + public async Task Move(Sense sense, BetweenPosition between) + { + await api.MoveSense(entryId, sense, between); + return 1; + } + + public async Task Remove(Sense sense) + { + await api.DeleteSense(entryId, sense.Id); + return 1; + } + + public Task Replace(Sense before, Sense after) + { + return SenseSync.Sync(entryId, after, before, api); + } + } } diff --git a/backend/FwLite/MiniLcm/SyncHelpers/ExampleSentenceSync.cs b/backend/FwLite/MiniLcm/SyncHelpers/ExampleSentenceSync.cs index ef2e6cf1a..f685e9650 100644 --- a/backend/FwLite/MiniLcm/SyncHelpers/ExampleSentenceSync.cs +++ b/backend/FwLite/MiniLcm/SyncHelpers/ExampleSentenceSync.cs @@ -11,25 +11,10 @@ public static async Task Sync(Guid entryId, IList beforeExampleSentences, IMiniLcmApi api) { - Func> add = async (api, afterExampleSentence) => - { - await api.CreateExampleSentence(entryId, senseId, afterExampleSentence); - return 1; - }; - Func> remove = async (api, beforeExampleSentence) => - { - await api.DeleteExampleSentence(entryId, senseId, beforeExampleSentence.Id); - return 1; - }; - Func> replace = - (api, beforeExampleSentence, afterExampleSentence) => - Sync(entryId, senseId, afterExampleSentence, beforeExampleSentence, api); - return await DiffCollection.Diff(api, + return await DiffCollection.Diff( beforeExampleSentences, afterExampleSentences, - add, - remove, - replace); + new ExampleSentencesDiffApi(api, entryId, senseId)); } public static async Task Sync(Guid entryId, @@ -64,4 +49,24 @@ public static async Task Sync(Guid entryId, if (patchDocument.Operations.Count == 0) return null; return new UpdateObjectInput(patchDocument); } + + private class ExampleSentencesDiffApi(IMiniLcmApi api, Guid entryId, Guid senseId) : ObjectWithIdCollectionDiffApi + { + public override async Task Add(ExampleSentence afterExampleSentence) + { + await api.CreateExampleSentence(entryId, senseId, afterExampleSentence); + return 1; + } + + public override async Task Remove(ExampleSentence beforeExampleSentence) + { + await api.DeleteExampleSentence(entryId, senseId, beforeExampleSentence.Id); + return 1; + } + + public override Task Replace(ExampleSentence beforeExampleSentence, ExampleSentence afterExampleSentence) + { + return Sync(entryId, senseId, afterExampleSentence, beforeExampleSentence, api); + } + } } diff --git a/backend/FwLite/MiniLcm/SyncHelpers/PartOfSpeechSync.cs b/backend/FwLite/MiniLcm/SyncHelpers/PartOfSpeechSync.cs index 9a7cf0bba..be6eff5a1 100644 --- a/backend/FwLite/MiniLcm/SyncHelpers/PartOfSpeechSync.cs +++ b/backend/FwLite/MiniLcm/SyncHelpers/PartOfSpeechSync.cs @@ -9,21 +9,10 @@ public static async Task Sync(PartOfSpeech[] currentPartsOfSpeech, PartOfSpeech[] previousPartsOfSpeech, IMiniLcmApi api) { - return await DiffCollection.Diff(api, + return await DiffCollection.Diff( previousPartsOfSpeech, currentPartsOfSpeech, - pos => pos.Id, - async (api, currentPos) => - { - await api.CreatePartOfSpeech(currentPos); - return 1; - }, - async (api, previousPos) => - { - await api.DeletePartOfSpeech(previousPos.Id); - return 1; - }, - (api, previousPos, currentPos) => Sync(previousPos, currentPos, api)); + new PartsOfSpeechDiffApi(api)); } public static async Task Sync(PartOfSpeech before, @@ -48,4 +37,24 @@ public static async Task Sync(PartOfSpeech before, if (patchDocument.Operations.Count == 0) return null; return new UpdateObjectInput(patchDocument); } + + private class PartsOfSpeechDiffApi(IMiniLcmApi api) : ObjectWithIdCollectionDiffApi + { + public override async Task Add(PartOfSpeech currentPos) + { + await api.CreatePartOfSpeech(currentPos); + return 1; + } + + public override async Task Remove(PartOfSpeech previousPos) + { + await api.DeletePartOfSpeech(previousPos.Id); + return 1; + } + + public override Task Replace(PartOfSpeech previousPos, PartOfSpeech currentPos) + { + return Sync(previousPos, currentPos, api); + } + } } diff --git a/backend/FwLite/MiniLcm/SyncHelpers/SemanticDomainSync.cs b/backend/FwLite/MiniLcm/SyncHelpers/SemanticDomainSync.cs index 3892e8ed1..b6bc793fc 100644 --- a/backend/FwLite/MiniLcm/SyncHelpers/SemanticDomainSync.cs +++ b/backend/FwLite/MiniLcm/SyncHelpers/SemanticDomainSync.cs @@ -9,21 +9,10 @@ public static async Task Sync(SemanticDomain[] currentSemanticDomains, SemanticDomain[] previousSemanticDomains, IMiniLcmApi api) { - return await DiffCollection.Diff(api, + return await DiffCollection.Diff( previousSemanticDomains, currentSemanticDomains, - pos => pos.Id, - async (api, currentPos) => - { - await api.CreateSemanticDomain(currentPos); - return 1; - }, - async (api, previousPos) => - { - await api.DeleteSemanticDomain(previousPos.Id); - return 1; - }, - (api, previousSemdom, currentSemdom) => Sync(previousSemdom, currentSemdom, api)); + new SemanticDomainsDiffApi(api)); } public static async Task Sync(SemanticDomain before, @@ -48,4 +37,24 @@ public static async Task Sync(SemanticDomain before, if (patchDocument.Operations.Count == 0) return null; return new UpdateObjectInput(patchDocument); } + + private class SemanticDomainsDiffApi(IMiniLcmApi api) : ObjectWithIdCollectionDiffApi + { + public override async Task Add(SemanticDomain currentSemDom) + { + await api.CreateSemanticDomain(currentSemDom); + return 1; + } + + public override async Task Remove(SemanticDomain previousSemDom) + { + await api.DeleteSemanticDomain(previousSemDom.Id); + return 1; + } + + public override Task Replace(SemanticDomain previousSemDom, SemanticDomain currentSemDom) + { + return Sync(previousSemDom, currentSemDom, api); + } + } } diff --git a/backend/FwLite/MiniLcm/SyncHelpers/SenseSync.cs b/backend/FwLite/MiniLcm/SyncHelpers/SenseSync.cs index 181107105..6c028cd56 100644 --- a/backend/FwLite/MiniLcm/SyncHelpers/SenseSync.cs +++ b/backend/FwLite/MiniLcm/SyncHelpers/SenseSync.cs @@ -17,24 +17,10 @@ public static async Task Sync(Guid entryId, afterSense.ExampleSentences, beforeSense.ExampleSentences, api); - changes += await DiffCollection.Diff(api, + changes += await DiffCollection.Diff( beforeSense.SemanticDomains, afterSense.SemanticDomains, - async (api, domain) => - { - await api.AddSemanticDomainToSense(beforeSense.Id, domain); - return 1; - }, - async (api, beforeDomain) => - { - await api.RemoveSemanticDomainFromSense(beforeSense.Id, beforeDomain.Id); - return 1; - }, - (_, beforeDomain, afterDomain) => - { - //do nothing, semantic domains are not editable here - return Task.FromResult(0); - }); + new SenseSemanticDomainsDiffApi(api, beforeSense.Id)); return changes + (updateObjectInput is null ? 0 : 1); } @@ -59,4 +45,24 @@ public static async Task Sync(Guid entryId, if (patchDocument.Operations.Count == 0) return null; return new UpdateObjectInput(patchDocument); } + + private class SenseSemanticDomainsDiffApi(IMiniLcmApi api, Guid senseId) : ObjectWithIdCollectionDiffApi + { + public override async Task Add(SemanticDomain domain) + { + await api.AddSemanticDomainToSense(senseId, domain); + return 1; + } + + public override async Task Remove(SemanticDomain beforeDomain) + { + await api.RemoveSemanticDomainFromSense(senseId, beforeDomain.Id); + return 1; + } + + public override Task Replace(SemanticDomain previousSemDom, SemanticDomain currentSemDom) + { + return Task.FromResult(0); + } + } } diff --git a/backend/FwLite/MiniLcm/SyncHelpers/WritingSystemSync.cs b/backend/FwLite/MiniLcm/SyncHelpers/WritingSystemSync.cs index 142ae97e7..548edd0e9 100644 --- a/backend/FwLite/MiniLcm/SyncHelpers/WritingSystemSync.cs +++ b/backend/FwLite/MiniLcm/SyncHelpers/WritingSystemSync.cs @@ -16,25 +16,10 @@ public static async Task Sync(WritingSystem[] currentWritingSystems, WritingSystem[] previousWritingSystems, IMiniLcmApi api) { - return await DiffCollection.Diff(api, + return await DiffCollection.Diff( previousWritingSystems, currentWritingSystems, - ws => (ws.WsId, ws.Type), - async (api, currentWs) => - { - await api.CreateWritingSystem(currentWs.Type, currentWs); - return 1; - }, - async (api, previousWs) => - { - // await api.DeleteWritingSystem(previousWs.Id); // Deleting writing systems is dangerous as it causes cascading data deletion. Needs careful thought. - // TODO: should we throw an exception? - return 0; - }, - async (api, previousWs, currentWs) => - { - return await Sync(currentWs, previousWs, api); - }); + new WritingSystemsDiffApi(api)); } public static async Task Sync(WritingSystem afterWs, WritingSystem beforeWs, IMiniLcmApi api) @@ -65,4 +50,30 @@ public static async Task Sync(WritingSystem afterWs, WritingSystem beforeWs if (patchDocument.Operations.Count == 0) return null; return new UpdateObjectInput(patchDocument); } + + private class WritingSystemsDiffApi(IMiniLcmApi api) : CollectionDiffApi + { + public override (WritingSystemId, WritingSystemType) GetId(WritingSystem value) + { + return (value.WsId, value.Type); + } + + public override async Task Add(WritingSystem currentWs) + { + await api.CreateWritingSystem(currentWs.Type, currentWs); + return 1; + } + + public override Task Remove(WritingSystem beforeDomain) + { + // await api.DeleteWritingSystem(previousWs.Id); // Deleting writing systems is dangerous as it causes cascading data deletion. Needs careful thought. + // TODO: should we throw an exception? + return Task.FromResult(0); + } + + public override Task Replace(WritingSystem previousWs, WritingSystem currentWs) + { + return Sync(currentWs, previousWs, api); + } + } }