Skip to content

Commit

Permalink
IActionValidatorFactory (#8)
Browse files Browse the repository at this point in the history
  • Loading branch information
YuriyDurov authored Oct 9, 2024
1 parent 85b7731 commit 315909f
Show file tree
Hide file tree
Showing 13 changed files with 324 additions and 223 deletions.
4 changes: 4 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[*.{cs,vb}]

# IDE0130: Namespace does not match folder structure
dotnet_diagnostic.IDE0130.severity = none
9 changes: 7 additions & 2 deletions Miscellaneous.sln
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "FluentValidation", "FluentV
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BitzArt.FluentValidation.Extensions.UnitTests", "tests\FluentValidation\BitzArt.FluentValidation.Extensions.UnitTests\BitzArt.FluentValidation.Extensions.UnitTests.csproj", "{9FDF435B-8ACD-48D5-986A-1B526D7F6E38}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BitzArt.LinqExtensions.Batching", "src\BitzArt.LinqExtensions.Batching\BitzArt.LinqExtensions.Batching.csproj", "{81AC0594-A0C5-4A02-8C4D-B8E139F16F5E}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BitzArt.LinqExtensions.Batching", "src\BitzArt.LinqExtensions.Batching\BitzArt.LinqExtensions.Batching.csproj", "{81AC0594-A0C5-4A02-8C4D-B8E139F16F5E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BitzArt.LinqExtensions.Batching.Tests", "tests\BitzArt.LinqExtensions.Batching.Tests\BitzArt.LinqExtensions.Batching.Tests.csproj", "{6DBA7894-9852-4DF1-B9E0-09D60D311F6B}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BitzArt.LinqExtensions.Batching.Tests", "tests\BitzArt.LinqExtensions.Batching.Tests\BitzArt.LinqExtensions.Batching.Tests.csproj", "{6DBA7894-9852-4DF1-B9E0-09D60D311F6B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{629938AD-8A1E-49C0-ABFB-724E564B0CCB}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,24 +1,16 @@
using System.Runtime.Serialization;

namespace FluentValidation;
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
@@ -0,0 +1,80 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using System.Reflection;

namespace FluentValidation;

public static class AddActionValidatorExtensions
{
public static IServiceCollection AddActionValidatorsFromAssemblyContaining<TAssemblyPointer>(this IServiceCollection services, Func<IServiceProvider, ActionType>? actionTypeResolver = null)
=> services.AddActionValidatorsFromAssemblyContaining(typeof(TAssemblyPointer), actionTypeResolver);

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

public static IServiceCollection AddActionValidatorsFromAssembly(this IServiceCollection services, Assembly assembly, Func<IServiceProvider, ActionType>? actionTypeResolver = null)
{
var validators = assembly
.DefinedTypes
.Where(x => x.IsClass && !x.IsAbstract)
.Where(x => x.GetInterfaces()
.Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IActionValidator<>)));

foreach (var validator in validators) services.AddActionValidator(validator, actionTypeResolver);

return services;
}

public static IServiceCollection AddActionValidator<TValidator>(this IServiceCollection services, Func<IServiceProvider, ActionType>? actionTypeResolver = null)
=> services.AddActionValidator(typeof(TValidator), actionTypeResolver);

public static IServiceCollection AddActionValidator(this IServiceCollection services, Type validatorType, Func<IServiceProvider, ActionType>? actionTypeResolver = 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");

services.TryAddScoped<IActionValidatorFactory>(serviceProvider => new ActionValidatorFactory(serviceProvider));

var interfaceDefinitions = validatorType.GetInterfaces().Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IActionValidator<>)).ToList();
if (interfaceDefinitions.Count == 0) throw new ArgumentException($"{validatorType.Name} does not implement IActionValidator<T>");

services.AddTransient(validatorType);
ActionValidatorFactory.ValidatorTypeMap[validatorType] = validatorType;

Func<IServiceProvider, ActionType?> finalActionTypeResolver = actionTypeResolver is not null ?
x => actionTypeResolver(x) :
x => null;

foreach (var interfaceDefinition in interfaceDefinitions)
{
var validationObjectType = interfaceDefinition.GetGenericArguments().First();
services.AddActionValidator(validatorType, validationObjectType, finalActionTypeResolver);
}

return services;
}

private static IServiceCollection AddActionValidator(this IServiceCollection services, Type validatorType, Type validationObjectType, Func<IServiceProvider, ActionType?> getActionType)
{
List<Type> registrationInterfaces =
[
typeof(IValidator<>).MakeGenericType(validationObjectType),
typeof(IActionValidator<>).MakeGenericType(validationObjectType)
];

foreach (var registrationInterface in registrationInterfaces)
{
services.AddScoped(registrationInterface, x =>
{
var factory = x.GetRequiredService<IActionValidatorFactory>();
var validator = factory.GetValidatorInternal(validatorType, getActionType: getActionType);

return validator;
});

ActionValidatorFactory.ValidatorTypeMap[registrationInterface] = validatorType;
}

return services;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
namespace FluentValidation;

public interface IActionValidator<T> : IValidator<T>, IActionValidator
{

}

public interface IActionValidator
{
public ActionType ActionType { get; set; }
public ActionType? Action { get; internal set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using Microsoft.Extensions.DependencyInjection;
using System.Collections.Concurrent;

namespace FluentValidation;

internal class ActionValidatorFactory(IServiceProvider serviceProvider) : IActionValidatorFactory
{
internal static ConcurrentDictionary<Type, Type> ValidatorTypeMap = [];

internal IServiceProvider _serviceProvider = serviceProvider;

private ActionType? _actionType = null;

public IActionValidator<T> GetValidator<T>(ActionType actionType)
=> (IActionValidator<T>)GetValidatorInternal(typeof(IActionValidator<T>), definedActionType: actionType);

public IActionValidator GetValidator(Type objectType, ActionType actionType)
=> GetValidatorInternal(typeof(IActionValidator<>).MakeGenericType(objectType), definedActionType: actionType);

public IActionValidator GetValidatorInternal(Type validatorType, Func<IServiceProvider, ActionType?>? actionTypeResolver = null, ActionType? definedActionType = null)
{
bool cleanup = false;
try
{
var implementationType = ValidatorTypeMap[validatorType]
?? throw new ArgumentException($"{validatorType.Name} is not registered as ActionValidator");

if (definedActionType.HasValue)
{
_actionType = definedActionType;
cleanup = true;
}

var validator = (IActionValidator)_serviceProvider.GetRequiredService(implementationType);

if (_actionType.HasValue)
{
validator.Action = _actionType!.Value;
return validator;
}

if (actionTypeResolver is not null)
{
validator.Action = actionTypeResolver(_serviceProvider);
return validator;
}

return validator;
}
finally
{
if (cleanup) _actionType = null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

namespace FluentValidation;

public interface IActionValidatorFactory
{
public IActionValidator<T> GetValidator<T>(ActionType actionType);

public IActionValidator GetValidator(Type objectType, ActionType actionType);

internal IActionValidator GetValidatorInternal(Type validatorType, Func<IServiceProvider, ActionType?> getActionType, ActionType? actionType = null);
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,12 @@
namespace FluentValidation;

public abstract class ActionValidator<T> : AbstractValidator<T>, IActionValidator
public abstract class ActionValidator<T> : AbstractValidator<T>, IActionValidator<T>
{
private ActionType? _actionType;
public ActionType ActionType
{
get => _actionType is not null ?
_actionType!.Value :
throw new ArgumentException("ActionType is not configured for this ActionValidator.");

set => _actionType = value;
}
public ActionType? Action { get; set; }

public IConditionBuilder When(ActionType actionType, Action action)
=> When(x => ActionType == actionType, action);
=> When(x => Action == actionType, action);

public IConditionBuilder Unless(ActionType actionType, Action action)
=> Unless(x => ActionType == actionType, action);
=> Unless(x => Action == actionType, action);
}
Loading

0 comments on commit 315909f

Please sign in to comment.