Skip to content

Commit

Permalink
Move order validation to FluentValidation
Browse files Browse the repository at this point in the history
  • Loading branch information
myieye committed Jan 13, 2025
1 parent a8d082f commit 617d644
Show file tree
Hide file tree
Showing 6 changed files with 34 additions and 15 deletions.
21 changes: 6 additions & 15 deletions backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,11 @@ public async Task BulkCreateEntries(IAsyncEnumerable<Entry> 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)
);
Expand Down Expand Up @@ -355,15 +359,11 @@ private IEnumerable<IChange> CreateEntryChanges(Entry entry, Dictionary<Guid, Se
sense.PartOfSpeechId = partOfSpeech.Id;
sense.PartOfSpeech = partOfSpeech.Name["en"] ?? string.Empty;
}
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");
sense.Order = senseOrder++;
yield return new CreateSenseChange(sense, entry.Id);
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);
}
Expand All @@ -379,8 +379,6 @@ await dataModel.AddChanges(ClientId,
..await entry.Senses.ToAsyncEnumerable()
.SelectMany((s, i) =>
{
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);
})
Expand Down Expand Up @@ -486,8 +484,6 @@ private async IAsyncEnumerable<IChange> 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);
}
Expand All @@ -505,9 +501,7 @@ private async IAsyncEnumerable<IChange> CreateSenseChanges(Guid entryId, Sense s

public async Task<Sense> 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());
Expand Down Expand Up @@ -558,9 +552,6 @@ public async Task<ExampleSentence> 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>(exampleSentence.Id) ?? throw new NullReferenceException();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
12 changes: 12 additions & 0 deletions backend/FwLite/MiniLcm/Validators/IOrderableValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using FluentValidation;
using MiniLcm.Models;

namespace MiniLcm.Validators;

public class IOrderableValidator : AbstractValidator<IOrderable>
{
public IOrderableValidator()
{
RuleFor(o => o.Order).Equal(default(double)).WithMessage("Order must not be set explicitly, it is managed internally.");
}
}
1 change: 1 addition & 0 deletions backend/FwLite/MiniLcm/Validators/SenseValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down

0 comments on commit 617d644

Please sign in to comment.