diff --git a/backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs b/backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs index a3e6716ec..7a5412991 100644 --- a/backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs +++ b/backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs @@ -324,7 +324,11 @@ public async Task BulkCreateEntries(IAsyncEnumerable entries) var partsOfSpeech = await PartsOfSpeech.ToDictionaryAsync(p => p.Id, p => p); await dataModel.AddChanges(ClientId, entries.ToBlockingEnumerable() - .SelectMany(entry => CreateEntryChanges(entry, semanticDomains, partsOfSpeech)) + .SelectMany(entry => + { + validators.ValidateAndThrow(entry).Wait(); //? + return CreateEntryChanges(entry, semanticDomains, partsOfSpeech); + }) //force entries to be created first, this avoids issues where references are created before the entry is created .OrderBy(c => c is CreateEntryChange ? 0 : 1) ); @@ -355,15 +359,11 @@ private IEnumerable CreateEntryChanges(Entry entry, Dictionary { - if (s.Order != default) // we don't anticipate this being necessary, so we'll be strict for now - throw new InvalidOperationException("Order should not be provided when creating a sense"); s.Order = i + 1; return CreateSenseChanges(entry.Id, s); }) @@ -486,8 +484,6 @@ private async IAsyncEnumerable CreateSenseChanges(Guid entryId, Sense s var exampleOrder = 1; foreach (var exampleSentence in sense.ExampleSentences) { - if (exampleSentence.Order != default) // we don't anticipate this being necessary, so we'll be strict for now - throw new InvalidOperationException("Order should not be provided when creating an example sentence"); exampleSentence.Order = exampleOrder++; yield return new CreateExampleSentenceChange(exampleSentence, sense.Id); } @@ -505,9 +501,7 @@ private async IAsyncEnumerable CreateSenseChanges(Guid entryId, Sense s public async Task CreateSense(Guid entryId, Sense sense, BetweenPosition? between = null) { - if (sense.Order != default) // we don't anticipate this being necessary, so we'll be strict for now - throw new InvalidOperationException("Order should not be provided when creating a sense"); - + await validators.ValidateAndThrow(sense); sense.Order = await OrderPicker.PickOrder(Senses.Where(s => s.EntryId == entryId), between); await validators.ValidateAndThrow(sense); await dataModel.AddChanges(ClientId, await CreateSenseChanges(entryId, sense).ToArrayAsync()); @@ -558,9 +552,6 @@ public async Task CreateExampleSentence(Guid entryId, BetweenPosition? between = null) { await validators.ValidateAndThrow(exampleSentence); - if (exampleSentence.Order != default) // we don't anticipate this being necessary, so we'll be strict for now - throw new InvalidOperationException("Order should not be provided when creating an example sentence"); - exampleSentence.Order = await OrderPicker.PickOrder(ExampleSentences.Where(s => s.SenseId == senseId), between); await dataModel.AddChange(ClientId, new CreateExampleSentenceChange(exampleSentence, senseId)); return await dataModel.GetLatest(exampleSentence.Id) ?? throw new NullReferenceException(); diff --git a/backend/FwLite/MiniLcm.Tests/Validators/ExampleSentenceValidatorTests.cs b/backend/FwLite/MiniLcm.Tests/Validators/ExampleSentenceValidatorTests.cs index 8ce002626..a62424a31 100644 --- a/backend/FwLite/MiniLcm.Tests/Validators/ExampleSentenceValidatorTests.cs +++ b/backend/FwLite/MiniLcm.Tests/Validators/ExampleSentenceValidatorTests.cs @@ -51,6 +51,13 @@ public void Fails_WhenNonEmptyFieldHasWsWithEmptyContent(string fieldName) _validator.TestValidate(example).ShouldHaveValidationErrorFor(fieldName); } + [Fact] + public void Fails_WhenOrderIsSet() + { + var exampleSentence = new ExampleSentence() { Id = Guid.NewGuid(), Order = 3 }; + _validator.TestValidate(exampleSentence).ShouldHaveValidationErrorFor(nameof(IOrderable.Order)); + } + private void SetProperty(ExampleSentence example, string propName, string content) { var propInfo = typeof(ExampleSentence).GetProperty(propName); diff --git a/backend/FwLite/MiniLcm.Tests/Validators/SenseValidatorTests.cs b/backend/FwLite/MiniLcm.Tests/Validators/SenseValidatorTests.cs index 69104e216..c776858d7 100644 --- a/backend/FwLite/MiniLcm.Tests/Validators/SenseValidatorTests.cs +++ b/backend/FwLite/MiniLcm.Tests/Validators/SenseValidatorTests.cs @@ -51,6 +51,13 @@ public void Fails_WhenNonEmptyFieldHasWsWithEmptyContent(string fieldName) _validator.TestValidate(sense).ShouldHaveValidationErrorFor(fieldName); } + [Fact] + public void Fails_WhenOrderIsSet() + { + var sense = new Sense() { Id = Guid.NewGuid(), Order = 3 }; + _validator.TestValidate(sense).ShouldHaveValidationErrorFor(nameof(IOrderable.Order)); + } + private void SetProperty(Sense sense, string propName, string content) { var propInfo = typeof(Sense).GetProperty(propName); diff --git a/backend/FwLite/MiniLcm/Validators/ExampleSentenceValidator.cs b/backend/FwLite/MiniLcm/Validators/ExampleSentenceValidator.cs index 557e7a25c..393a4016c 100644 --- a/backend/FwLite/MiniLcm/Validators/ExampleSentenceValidator.cs +++ b/backend/FwLite/MiniLcm/Validators/ExampleSentenceValidator.cs @@ -10,6 +10,7 @@ public ExampleSentenceValidator() RuleFor(es => es.DeletedAt).Null(); RuleFor(es => es.Sentence).NoEmptyValues(); RuleFor(es => es.Translation).NoEmptyValues(); + Include(new IOrderableValidator()); } public ExampleSentenceValidator(Sense sense) : this() diff --git a/backend/FwLite/MiniLcm/Validators/IOrderableValidator.cs b/backend/FwLite/MiniLcm/Validators/IOrderableValidator.cs new file mode 100644 index 000000000..857ea0001 --- /dev/null +++ b/backend/FwLite/MiniLcm/Validators/IOrderableValidator.cs @@ -0,0 +1,12 @@ +using FluentValidation; +using MiniLcm.Models; + +namespace MiniLcm.Validators; + +public class IOrderableValidator : AbstractValidator +{ + public IOrderableValidator() + { + RuleFor(o => o.Order).Equal(default(double)).WithMessage("Order must not be set explicitly, it is managed internally."); + } +} diff --git a/backend/FwLite/MiniLcm/Validators/SenseValidator.cs b/backend/FwLite/MiniLcm/Validators/SenseValidator.cs index 34e28f713..25885d5b8 100644 --- a/backend/FwLite/MiniLcm/Validators/SenseValidator.cs +++ b/backend/FwLite/MiniLcm/Validators/SenseValidator.cs @@ -14,6 +14,7 @@ public SenseValidator() // RuleFor(s => s.PartOfSpeechId).SetValidator(new IsCanonicalPartOfSpeechGuidValidator()); // Can't do this statelessly, as we'd need a full PartOfSpeech object to check if it's predefined or not RuleForEach(s => s.SemanticDomains).SetValidator(new SemanticDomainValidator()); RuleForEach(s => s.ExampleSentences).SetValidator(sense => new ExampleSentenceValidator(sense)); + Include(new IOrderableValidator()); } public SenseValidator(Entry entry): this()