AppAny.HotChocolate.FluentValidation

January 10, 2023 ยท View on GitHub

License Nuget Downloads Tests codecov

Feature-rich, but simple, fast and memory efficient input field HotChocolate + FluentValidation integration โšก๏ธ

๐Ÿ”ง Installation

$> dotnet add package AppAny.HotChocolate.FluentValidation

๐Ÿ’ก Features

๐Ÿšฉ You don't pay for validation middleware if the field has no validatable inputs

๐Ÿšฉ You are not validating, and even trying to validate empty or not marked as validatable inputs

๐Ÿšฉ Most of extensibility points are just composable delegates

๐Ÿšฉ Fine-tuning of validation for each field: conditional validation skipping, multiple validators or error mappers per input

๐Ÿšฉ Strongly typed ValidationStrategy<T> support

๐Ÿšฉ First-class attribute-based approach support

๐ŸŽจ Usage

โœ… Add FluentValidation validator

public class ExampleInput
{
  public string Example { get; set; }
}

public class ExampleInputValidator : AbstractValidator<ExampleInput>
{
  public ExampleInputValidator()
  {
    RuleFor(input => input.ExampleProperty)
      .NotEmpty()
      .WithMessage("Property is empty");
  }
}

โœ… Configure HotChocolate + FluentValidation integration

# Since 10.2.0 https://github.com/FluentValidation/FluentValidation/releases/tag/10.2.0
services.AddFluentValidation();

services.AddGraphQLServer()
  .AddFluentValidation();

descriptor.Field(x => x.Example(default!))
  // Explicit over implicit preferred
  // You have to add .UseFluentValidation()/attribute to all arguments requiring validation
  .Argument("input", argument => argument.UseFluentValidation());

... Example([UseFluentValidation] ExampleInput input) { ... }

โœ… Extend and customize

services.AddGraphQLServer()
  .AddFluentValidation(options =>
  {
    options.SkipValidation(...)
      .UseErrorMapper(...)
      .UseInputValidators(...);
  });

descriptor.Field(x => x.Example(default!))
  .Argument("input", argument => argument.UseFluentValidation(options =>
  {
    options.SkipValidation(...)
      .UseErrorMapper(...)
      .UseInputValidators(...)
      .UseValidationStrategy(...)
      .UseValidator<ExampleInputValidator>()
      .UseValidator<ExampleInput, ExampleInputValidator>()
      .UseValidator<ExampleInput, ExampleInputValidator>(strategy =>
      {
        strategy.IncludeProperties(input => input.ExampleProperty);
        // ...
      });
  }));

... Example([UseFluentValidation, UseValidator((typeof(ExampleInputValidator))] ExampleInput input) { ... }

๐Ÿ“ Docs

โ™ฟ๏ธ Benchmarks ๐Ÿš€

๐Ÿšง I swear I will check correctness, run these benchmarks on my own environment and only after that I will make conclusions ๐Ÿšง

Breaking changes

  • From 0.10.x to 0.11.x
    • Replace ValidationDefaults.Interceptors static class with ValidationDefaults.Interceptor property
    • Replace ValidationInterceptors static class with ValidationInterceptor : TypeInterceptor class
  • From 0.9.x to 0.10.x
    • Update HC version to 13 preview
  • From 0.6.x to 0.7.x
    • Default input validator throws InvalidOperationException if argument has [UseFluentValidation], but no validator registered in IServiceCollection