Skip to content

Commit

Permalink
Keyed ActionValidators (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
YuriyDurov authored Oct 7, 2024
1 parent e3d9eb3 commit 85b7731
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -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";
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BitzArt.EnumToMemberValue" Version="1.0.0" />
<PackageReference Include="FluentValidation" Version="11.9.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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<TAssemblyPointer>(this IServiceCollection services, Func<IServiceProvider, ActionType> getActionType)
public static IServiceCollection AddActionValidatorsFromAssemblyContaining<TAssemblyPointer>(this IServiceCollection services, Func<IServiceProvider, ActionType>? getActionType = null)
=> services.AddActionValidatorsFromAssemblyContaining(typeof(TAssemblyPointer), getActionType);

public static IServiceCollection AddActionValidatorsFromAssemblyContaining(this IServiceCollection services, Type type, Func<IServiceProvider, ActionType> getActionType)
public static IServiceCollection AddActionValidatorsFromAssemblyContaining(this IServiceCollection services, Type type, Func<IServiceProvider, ActionType>? getActionType = null)
=> services.AddActionValidatorsFromAssembly(type.Assembly, getActionType);

public static IServiceCollection AddActionValidatorsFromAssembly(this IServiceCollection services, Assembly assembly, Func<IServiceProvider, ActionType> getActionType)
public static IServiceCollection AddActionValidatorsFromAssembly(this IServiceCollection services, Assembly assembly, Func<IServiceProvider, ActionType>? getActionType = null)
{
var validators = assembly
.DefinedTypes
Expand All @@ -23,7 +25,7 @@ public static IServiceCollection AddActionValidatorsFromAssembly(this IServiceCo
return services;
}

public static IServiceCollection AddActionValidator(this IServiceCollection services, Type validatorType, Func<IServiceProvider, ActionType> getActionType)
public static IServiceCollection AddActionValidator(this IServiceCollection services, Type validatorType, Func<IServiceProvider, ActionType>? 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");
Expand All @@ -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<TService, TEnum>(
this IServiceCollection services,
Type registrationType,
Func<IServiceProvider, TService> implementationFactory,
Func<TEnum, string> enumStringValueFactory,
Action<TService, TEnum> applyKeyAction,
ServiceLifetime serviceLifetime)
where TService : class
where TEnum : struct, Enum
{
var enumValues = Enum.GetValues<TEnum>();

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));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using BitzArt.EnumToMemberValue;
using FluentValidation.Models;
using Microsoft.Extensions.DependencyInjection;

Expand Down Expand Up @@ -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<IValidator<TestEntity>>(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<IValidator<TestEntity>>(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<IValidator<TestEntity>>(resolveKey);

var expectedActionType = resolveKey.ToEnum<ActionType>();

Assert.NotNull(validator);
Assert.True(validator is TestEntityValidator);
Assert.Equal(expectedActionType, ((TestEntityValidator)validator).ActionType);
}
}

0 comments on commit 85b7731

Please sign in to comment.