From 3e8b3853cab032e7c22d30427b6ed8ca76b5dc41 Mon Sep 17 00:00:00 2001 From: Yuriy Durov Date: Mon, 7 Oct 2024 14:04:52 +0400 Subject: [PATCH] Keyed ActionValidators --- .../ActionTypes.cs | 16 ++++ ...BitzArt.FluentValidation.Extensions.csproj | 1 + .../Enums/ActionType.cs | 15 +++- .../AddActionValidatorsExtension.cs | 62 +++++++++++++-- .../AddActionValidatorsExtensionTests.cs | 78 +++++++++++++++++++ 5 files changed, 165 insertions(+), 7 deletions(-) create mode 100644 src/FluentValidation/BitzArt.FluentValidation.Extensions/ActionTypes.cs diff --git a/src/FluentValidation/BitzArt.FluentValidation.Extensions/ActionTypes.cs b/src/FluentValidation/BitzArt.FluentValidation.Extensions/ActionTypes.cs new file mode 100644 index 0000000..ef0fea3 --- /dev/null +++ b/src/FluentValidation/BitzArt.FluentValidation.Extensions/ActionTypes.cs @@ -0,0 +1,16 @@ +namespace FluentValidation; + +public static class ActionTypes +{ + public const string Get = "Get"; + + public const string Create = "Create"; + + public const string Update = "Update"; + + public const string Patch = "Patch"; + + public const string Options = "Options"; + + public const string Delete = "Delete"; +} diff --git a/src/FluentValidation/BitzArt.FluentValidation.Extensions/BitzArt.FluentValidation.Extensions.csproj b/src/FluentValidation/BitzArt.FluentValidation.Extensions/BitzArt.FluentValidation.Extensions.csproj index f896082..2e29046 100644 --- a/src/FluentValidation/BitzArt.FluentValidation.Extensions/BitzArt.FluentValidation.Extensions.csproj +++ b/src/FluentValidation/BitzArt.FluentValidation.Extensions/BitzArt.FluentValidation.Extensions.csproj @@ -16,6 +16,7 @@ + diff --git a/src/FluentValidation/BitzArt.FluentValidation.Extensions/Enums/ActionType.cs b/src/FluentValidation/BitzArt.FluentValidation.Extensions/Enums/ActionType.cs index b6f44fe..daa7797 100644 --- a/src/FluentValidation/BitzArt.FluentValidation.Extensions/Enums/ActionType.cs +++ b/src/FluentValidation/BitzArt.FluentValidation.Extensions/Enums/ActionType.cs @@ -1,11 +1,24 @@ -namespace FluentValidation; +using System.Runtime.Serialization; + +namespace FluentValidation; public enum ActionType : byte { + [EnumMember(Value = ActionTypes.Get)] Get = 1, + + [EnumMember(Value = ActionTypes.Create)] Create = 2, + + [EnumMember(Value = ActionTypes.Update)] Update = 3, + + [EnumMember(Value = ActionTypes.Patch)] Patch = 4, + + [EnumMember(Value = ActionTypes.Options)] Options = 5, + + [EnumMember(Value = ActionTypes.Delete)] Delete = 6 } diff --git a/src/FluentValidation/BitzArt.FluentValidation.Extensions/Extensions/AddActionValidatorsExtension.cs b/src/FluentValidation/BitzArt.FluentValidation.Extensions/Extensions/AddActionValidatorsExtension.cs index e56808a..f9b43df 100644 --- a/src/FluentValidation/BitzArt.FluentValidation.Extensions/Extensions/AddActionValidatorsExtension.cs +++ b/src/FluentValidation/BitzArt.FluentValidation.Extensions/Extensions/AddActionValidatorsExtension.cs @@ -1,17 +1,19 @@ -using Microsoft.Extensions.DependencyInjection; +using BitzArt.EnumToMemberValue; +using Microsoft.Extensions.DependencyInjection; +using System; using System.Reflection; namespace FluentValidation; public static class AddActionValidatorsExtension { - public static IServiceCollection AddActionValidatorsFromAssemblyContaining(this IServiceCollection services, Func getActionType) + public static IServiceCollection AddActionValidatorsFromAssemblyContaining(this IServiceCollection services, Func? getActionType = null) => services.AddActionValidatorsFromAssemblyContaining(typeof(TAssemblyPointer), getActionType); - public static IServiceCollection AddActionValidatorsFromAssemblyContaining(this IServiceCollection services, Type type, Func getActionType) + public static IServiceCollection AddActionValidatorsFromAssemblyContaining(this IServiceCollection services, Type type, Func? getActionType = null) => services.AddActionValidatorsFromAssembly(type.Assembly, getActionType); - public static IServiceCollection AddActionValidatorsFromAssembly(this IServiceCollection services, Assembly assembly, Func getActionType) + public static IServiceCollection AddActionValidatorsFromAssembly(this IServiceCollection services, Assembly assembly, Func? getActionType = null) { var validators = assembly .DefinedTypes @@ -23,7 +25,7 @@ public static IServiceCollection AddActionValidatorsFromAssembly(this IServiceCo return services; } - public static IServiceCollection AddActionValidator(this IServiceCollection services, Type validatorType, Func getActionType) + public static IServiceCollection AddActionValidator(this IServiceCollection services, Type validatorType, Func? getActionType = null) { if (validatorType is null) throw new ArgumentException($"{nameof(validatorType)} must not be null"); if (validatorType.BaseType!.GetGenericTypeDefinition() != typeof(ActionValidator<>)) throw new ArgumentException($"{validatorType.Name} is not assignable to ActionValidator"); @@ -34,13 +36,61 @@ public static IServiceCollection AddActionValidator(this IServiceCollection serv var registrationType = typeof(IValidator<>).MakeGenericType(validationObjectType); services.AddTransient(validatorType); - services.AddScoped(registrationType, x => + + if (getActionType is not null) services.AddScoped(registrationType, x => { var validator = x.GetRequiredService(validatorType); (validator as IActionValidator)!.ActionType = getActionType.Invoke(x); return validator; }); + services.AddKeyedForEnum( + registrationType, + x => (IActionValidator)x.GetRequiredService(validatorType), + (ActionType type) => type.ToMemberValue(), + (validator, key) => validator.ActionType = key, + ServiceLifetime.Scoped); + return services; } + + private static void AddKeyedForEnum( + this IServiceCollection services, + Type registrationType, + Func implementationFactory, + Func enumStringValueFactory, + Action applyKeyAction, + ServiceLifetime serviceLifetime) + where TService : class + where TEnum : struct, Enum + { + var enumValues = Enum.GetValues(); + + foreach (var enumValue in enumValues) + { + services.Add( + new ServiceDescriptor( + registrationType, + enumValue, + (x, key) => + { + var service = implementationFactory(x); + applyKeyAction(service, enumValue); + return service; + }, + serviceLifetime)); + + services.Add( + new ServiceDescriptor( + registrationType, + enumStringValueFactory(enumValue), + (x, key) => + { + var service = implementationFactory(x); + applyKeyAction(service, enumValue); + return service; + }, + serviceLifetime)); + } + } } diff --git a/tests/FluentValidation/BitzArt.FluentValidation.Extensions.UnitTests/Tests/AddActionValidatorsExtensionTests.cs b/tests/FluentValidation/BitzArt.FluentValidation.Extensions.UnitTests/Tests/AddActionValidatorsExtensionTests.cs index 1b76adb..341efa4 100644 --- a/tests/FluentValidation/BitzArt.FluentValidation.Extensions.UnitTests/Tests/AddActionValidatorsExtensionTests.cs +++ b/tests/FluentValidation/BitzArt.FluentValidation.Extensions.UnitTests/Tests/AddActionValidatorsExtensionTests.cs @@ -1,3 +1,4 @@ +using BitzArt.EnumToMemberValue; using FluentValidation.Models; using Microsoft.Extensions.DependencyInjection; @@ -107,4 +108,81 @@ public void AddActionValidatorsFromAssemblyContainingGeneric_WithFlatActionType_ var validatorCasted = (TestEntityValidator)validator; Assert.Equal(actionType, validatorCasted.ActionType); } + + [Theory] + [InlineData(ActionType.Get)] + [InlineData(ActionType.Create)] + [InlineData(ActionType.Update)] + [InlineData(ActionType.Patch)] + [InlineData(ActionType.Options)] + [InlineData(ActionType.Delete)] + public void AddActionValidator_WithoutSpecifyingActionType_ValidatorAccessibleByActionType(ActionType resolveKey) + { + // Arrange + IServiceCollection services = new ServiceCollection(); + var validatorType = typeof(TestEntityValidator); + + // Act + services.AddActionValidator(validatorType); + + // Assert + var serviceProvider = services.BuildServiceProvider(); + var validator = serviceProvider.GetRequiredKeyedService>(resolveKey); + + Assert.NotNull(validator); + Assert.True(validator is TestEntityValidator); + Assert.Equal(resolveKey, ((TestEntityValidator)validator).ActionType); + } + + [Theory] + [InlineData(ActionType.Get)] + [InlineData(ActionType.Create)] + [InlineData(ActionType.Update)] + [InlineData(ActionType.Patch)] + [InlineData(ActionType.Options)] + [InlineData(ActionType.Delete)] + public void AddActionValidator_WithActionType_ValidatorStillAccessibleByActionType(ActionType resolveKey) + { + // Arrange + IServiceCollection services = new ServiceCollection(); + var validatorType = typeof(TestEntityValidator); + + // Act + services.AddActionValidator(validatorType, x => default); + + // Assert + var serviceProvider = services.BuildServiceProvider(); + var validator = serviceProvider.GetRequiredKeyedService>(resolveKey); + + Assert.NotNull(validator); + Assert.True(validator is TestEntityValidator); + Assert.Equal(resolveKey, ((TestEntityValidator)validator).ActionType); + } + + [Theory] + [InlineData(ActionTypes.Get)] + [InlineData(ActionTypes.Create)] + [InlineData(ActionTypes.Update)] + [InlineData(ActionTypes.Patch)] + [InlineData(ActionTypes.Options)] + [InlineData(ActionTypes.Delete)] + public void AddActionValidator_WithoutSpecifyingActionType_ValidatorAccessibleByActionTypeEnumMemberValue(string resolveKey) + { + // Arrange + IServiceCollection services = new ServiceCollection(); + var validatorType = typeof(TestEntityValidator); + + // Act + services.AddActionValidator(validatorType); + + // Assert + var serviceProvider = services.BuildServiceProvider(); + var validator = serviceProvider.GetRequiredKeyedService>(resolveKey); + + var expectedActionType = resolveKey.ToEnum(); + + Assert.NotNull(validator); + Assert.True(validator is TestEntityValidator); + Assert.Equal(expectedActionType, ((TestEntityValidator)validator).ActionType); + } } \ No newline at end of file