Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
sergeyshaykhullin committed Feb 16, 2021
2 parents 3dea1f9 + 0ec3fac commit bca328a
Show file tree
Hide file tree
Showing 14 changed files with 143 additions and 37 deletions.
14 changes: 1 addition & 13 deletions .github/workflows/hotchocolate.fluentvalidation.codeql.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
name: "CodeQL"
name: HotChocolate | FluentValidation | CodeQL

on:
push:
branches: [ main ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ main ]
schedule:
- cron: '0 0 * * *'
Expand All @@ -31,16 +30,5 @@ jobs:
- name: Autobuild
uses: github/codeql-action/autobuild@v1

# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl

# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language

#- run: |
# make bootstrap
# make release

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
8 changes: 7 additions & 1 deletion .github/workflows/hotchocolate.fluentvalidation.tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,10 @@ jobs:
dotnet-version: 5.0.x

- name: Hotchocolate | FluentValidation | Tests
run: dotnet test
run: dotnet test /p:CollectCoverage=true /p:CoverletOutput=coverage/ /p:CoverletOutputFormat=lcov

- name: Hotchocolate | FluentValidation | Tests | Coverage | Publish
uses: coverallsapp/github-action@v1.0.1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
path-to-lcov: tests/AppAny.HotChocolate.FluentValidation.Tests/coverage/coverage.info
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# AppAny.HotChocolate.FluentValidation

[![Nuget](https://img.shields.io/nuget/v/AppAny.HotChocolate.FluentValidation.svg)](https://www.nuget.org/packages/AppAny.HotChocolate.FluentValidation) ![Hotchocolate | FluentValidation](https://github.com/appany/AppAny.HotChocolate.FluentValidation/workflows/Hotchocolate%20%7C%20FluentValidation/badge.svg)
[![Nuget](https://img.shields.io/nuget/v/AppAny.HotChocolate.FluentValidation.svg)](https://www.nuget.org/packages/AppAny.HotChocolate.FluentValidation) ![Hotchocolate | FluentValidation](https://github.com/appany/AppAny.HotChocolate.FluentValidation/workflows/Hotchocolate%20%7C%20FluentValidation/badge.svg) [![Coverage Status](https://coveralls.io/repos/github/appany/AppAny.HotChocolate.FluentValidation/badge.svg?branch=main)](https://coveralls.io/github/appany/AppAny.HotChocolate.FluentValidation?branch=main)

Feature-rich, simple, fast and memory efficient input field `HotChocolate` + `FluentValidation` integration

Expand Down
13 changes: 10 additions & 3 deletions src/Attributes/SkipValidationAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
using System.Threading.Tasks;

namespace AppAny.HotChocolate.FluentValidation
{
public sealed class SkipValidationAttribute : FluentValidationAttribute
public class SkipValidationAttribute : FluentValidationAttribute
{
public override void Configure(ArgumentValidationBuilder builder)
public sealed override void Configure(ArgumentValidationBuilder builder)
{
builder.SkipValidation();
builder.SkipValidation(SkipValidation);
}

public virtual ValueTask<bool> SkipValidation(SkipValidationContext skipValidationContext)
{
return ValidationDefaults.SkipValidation.Skip(skipValidationContext);
}
}
}
2 changes: 2 additions & 0 deletions src/Extensions/ArgumentsExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using HotChocolate.Types;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

namespace AppAny.HotChocolate.FluentValidation
{
internal static class ArgumentsExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IInputField? TryGetArgument(this IDictionary<string, IInputField> arguments, string name)
{
return arguments.TryGetValue(name, out var data)
Expand Down
4 changes: 4 additions & 0 deletions src/Extensions/ExtensionDataExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using HotChocolate;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

namespace AppAny.HotChocolate.FluentValidation
{
Expand All @@ -18,6 +19,7 @@ public static ObjectFieldValidationOptions GetOrCreateObjectFieldOptions(this Ex
return options;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ObjectFieldValidationOptions GetObjectFieldOptions(
this IReadOnlyDictionary<string, object?> contextData)
{
Expand Down Expand Up @@ -45,6 +47,7 @@ public static ArgumentValidationOptions GetOrCreateArgumentOptions(this Extensio
return options;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ArgumentValidationOptions GetArgumentOptions(this IReadOnlyDictionary<string, object?> contextData)
{
return (ArgumentValidationOptions)contextData[ValidationDefaults.ArgumentOptionsKey]!;
Expand All @@ -63,6 +66,7 @@ public static bool ShouldValidateArgument(this IReadOnlyDictionary<string, objec
return contextData.TryGetArgumentOptions() is not null;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ValidationOptions GetValidationOptions(this IDictionary<string, object?> contextData)
{
return (ValidationOptions)contextData[ValidationDefaults.ValidationOptionsKey]!;
Expand Down
2 changes: 2 additions & 0 deletions src/Extensions/InputFieldExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using FluentValidation;
using HotChocolate.Types;

Expand All @@ -9,6 +10,7 @@ internal static class InputFieldExtensions
{
private static readonly ConcurrentDictionary<Type, Type> ArgumentTypeToValidatorType = new();

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Type GetGenericValidatorType(this IInputField inputField)
{
return ArgumentTypeToValidatorType.GetOrAdd(
Expand Down
2 changes: 2 additions & 0 deletions src/Extensions/ValidationResultExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using System.Runtime.CompilerServices;
using FluentValidation.Results;

namespace AppAny.HotChocolate.FluentValidation
{
internal static class ValidationResultExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void MergeFailures(this ValidationResult validationResult, ValidationResult validatorResult)
{
for (var index = 0; index < validatorResult.Errors.Count; index++)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
<PackageReference Include="FairyBread" Version="4.0.0" />
<PackageReference Include="FairyBread" Version="4.1.0" />
<PackageReference Include="FluentChoco" Version="2.0.0" />
</ItemGroup>

Expand Down
32 changes: 16 additions & 16 deletions tests/AppAny.HotChocolate.FluentValidation.Benchmarks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,30 +19,30 @@ Intel Core i7-9700K CPU 3.60GHz (Coffee Lake), 1 CPU, 8 logical and 8 physical c

| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---------------------------- |----------:|----------:|----------:|------:|--------:|-------:|-------:|------:|----------:|
| RunWithoutValidation | 7.841 μs | 0.0511 μs | 0.0478 μs | 0.33 | 0.00 | 1.2512 | 0.0153 | - | 7.66 KB |
| RunWithValidation | 23.724 μs | 0.3094 μs | 0.2894 μs | 1.00 | 0.00 | 1.7090 | 0.0305 | - | 10.44 KB |
| RunWithFairyBreadValidation | 27.090 μs | 0.1547 μs | 0.1447 μs | 1.14 | 0.02 | 1.8616 | 0.0305 | - | 11.51 KB |
| RunWithoutValidation | 7.961 μs | 0.0383 μs | 0.0358 μs | 0.33 | 0.00 | 1.2512 | 0.0153 | - | 7.66 KB |
| RunWithValidation | 24.140 μs | 0.2255 μs | 0.2109 μs | 1.00 | 0.00 | 1.7090 | 0.0305 | - | 10.44 KB |
| RunWithFairyBreadValidation | 25.801 μs | 0.3395 μs | 0.3010 μs | 1.07 | 0.02 | 1.8616 | 0.0305 | - | 11.5 KB |

## ScopedValidationBenchmarks

| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---------------------------- |----------:|----------:|----------:|------:|--------:|-------:|-------:|------:|----------:|
| RunWithoutValidation | 8.064 μs | 0.0755 μs | 0.0669 μs | 0.23 | 0.00 | 1.2512 | 0.0153 | - | 7.66 KB |
| RunWithValidation | 34.893 μs | 0.6829 μs | 0.6707 μs | 1.00 | 0.00 | 2.0752 | - | - | 12.67 KB |
| RunWithFairyBreadValidation | 38.008 μs | 0.2207 μs | 0.1956 μs | 1.09 | 0.02 | 2.6245 | 0.0610 | - | 16.23 KB |
| Method | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---------------------------- |----------:|----------:|----------:|------:|-------:|-------:|------:|----------:|
| RunWithoutValidation | 8.112 μs | 0.0641 μs | 0.0569 μs | 0.23 | 1.2512 | 0.0153 | - | 7.66 KB |
| RunWithValidation | 34.869 μs | 0.1993 μs | 0.1766 μs | 1.00 | 2.0752 | 0.0610 | - | 12.67 KB |
| RunWithFairyBreadValidation | 39.196 μs | 0.3058 μs | 0.2711 μs | 1.12 | 2.6245 | 0.0610 | - | 16.23 KB |

## EmptyInputsValidationBenchmarks

| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---------------------------- |---------:|----------:|----------:|------:|--------:|-------:|-------:|------:|----------:|
| RunWithoutValidation | 7.763 μs | 0.0540 μs | 0.0505 μs | 0.98 | 0.01 | 1.2512 | 0.0153 | - | 7.64 KB |
| RunWithValidation | 7.905 μs | 0.0861 μs | 0.0805 μs | 1.00 | 0.00 | 1.2512 | 0.0153 | - | 7.64 KB |
| RunWithFairyBreadValidation | 9.039 μs | 0.1150 μs | 0.1076 μs | 1.14 | 0.02 | 1.2970 | 0.0153 | - | 7.93 KB |
| Method | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---------------------------- |---------:|----------:|----------:|------:|-------:|-------:|------:|----------:|
| RunWithoutValidation | 7.983 μs | 0.0794 μs | 0.0743 μs | 0.99 | 1.2512 | 0.0153 | - | 7.64 KB |
| RunWithValidation | 8.069 μs | 0.0555 μs | 0.0519 μs | 1.00 | 1.2512 | 0.0153 | - | 7.64 KB |
| RunWithFairyBreadValidation | 9.020 μs | 0.0870 μs | 0.0814 μs | 1.12 | 1.2970 | 0.0153 | - | 7.93 KB |

## NullInputsValidationBenchmarks

| Method | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---------------------------- |---------:|----------:|----------:|------:|-------:|-------:|------:|----------:|
| RunWithoutValidation | 7.878 μs | 0.0423 μs | 0.0395 μs | 0.91 | 1.2512 | 0.0153 | - | 7.65 KB |
| RunWithValidation | 8.630 μs | 0.0676 μs | 0.0599 μs | 1.00 | 1.2512 | 0.0153 | - | 7.65 KB |
| RunWithFairyBreadValidation | 8.860 μs | 0.1183 μs | 0.1107 μs | 1.03 | 1.2970 | 0.0153 | - | 7.94 KB |
| RunWithoutValidation | 7.943 μs | 0.0745 μs | 0.0696 μs | 0.93 | 1.2512 | 0.0153 | - | 7.65 KB |
| RunWithValidation | 8.520 μs | 0.0366 μs | 0.0325 μs | 1.00 | 1.2512 | 0.0153 | - | 7.65 KB |
| RunWithFairyBreadValidation | 9.157 μs | 0.0487 μs | 0.0431 μs | 1.07 | 1.2970 | 0.0153 | - | 7.94 KB |
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="coverlet.collector" Version="3.0.2">
<PackageReference Include="coverlet.msbuild" Version="3.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.Threading.Tasks;
using HotChocolate.Types;

namespace AppAny.HotChocolate.FluentValidation.Tests
{
public class CustomSkipValidationAttribute : SkipValidationAttribute
{
public const string SkipName = "Custom";

public override ValueTask<bool> SkipValidation(SkipValidationContext skipValidationContext)
{
var argumentValue = skipValidationContext
.MiddlewareContext
.ArgumentValue<TestPersonInput>(skipValidationContext.Argument.Name);

return new ValueTask<bool>(argumentValue.Name == SkipName);
}
}

public class TestCustomSkipValidationMutation : ObjectType
{
protected override void Configure(IObjectTypeDescriptor descriptor)
{
descriptor.Field<TestCustomSkipValidationMutation>(
field => field.Test(default!)).Type<StringType>();
}

public string Test([UseFluentValidation, CustomSkipValidation] TestPersonInput input)
{
return "test";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ public static class Mutations

public const string WithEmptyNameAndSecondInput =
"mutation { test(input: { name: \"\" }, input2: { name: \"\" }) }";

public static string WithName(string name)
{
return $"mutation {{ test(input: {{ name: \"{name}\" }}) }}";
}
}

public static ValueTask<IRequestExecutor> CreateRequestExecutor(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ public async Task Should_SkipValidation_AttributeConfiguration()
},
services =>
{
services.AddTransient<NotEmptyNameValidator>();
services.AddTransient<IValidator<TestPersonInput>, NotEmptyNameValidator>();
});

var result = Assert.IsType<QueryResult>(
Expand All @@ -308,6 +308,63 @@ public async Task Should_SkipValidation_AttributeConfiguration()
Assert.Null(result.Errors);
}

[Fact]
public async Task Should_CustomSkipValidation_AttributeConfiguration()
{
var executor = await TestSetup.CreateRequestExecutor(builder =>
{
builder.AddFluentValidation(opt => opt.UseDefaultErrorMapper())
.AddMutationType(new TestCustomSkipValidationMutation());
},
services =>
{
services.AddTransient<IValidator<TestPersonInput>, NotEmptyNameValidator>();
});

var result = Assert.IsType<QueryResult>(
await executor.ExecuteAsync(TestSetup.Mutations.WithEmptyName));

result.AssertNullResult();

Assert.Collection(result.Errors,
name =>
{
Assert.Equal(ValidationDefaults.Code, name.Code);
Assert.Equal(NotEmptyNameValidator.Message, name.Message);
Assert.Collection(name.Extensions,
code =>
{
Assert.Equal(ValidationDefaults.ExtensionKeys.CodeKey, code.Key);
Assert.Equal(ValidationDefaults.Code, code.Value);
});
});
}

[Fact]
public async Task Should_CustomSkipValidation_Skip()
{
var executor = await TestSetup.CreateRequestExecutor(builder =>
{
builder.AddFluentValidation(opt => opt.UseDefaultErrorMapper())
.AddMutationType(new TestCustomSkipValidationMutation());
},
services =>
{
services.AddTransient<IValidator<TestPersonInput>, NotEmptyNameValidator>();
});

var result = Assert.IsType<QueryResult>(
await executor.ExecuteAsync(TestSetup.Mutations.WithName("Custom")));

var (key, value) = Assert.Single(result.Data);

Assert.Equal("test", key);
Assert.Equal("test", value);

Assert.Null(result.Errors);
}

[Fact]
public async Task Should_Use_AttributeConfiguration_CustomValidators()
{
Expand Down

0 comments on commit bca328a

Please sign in to comment.