Skip to content

Commit

Permalink
chore: Refresh from 7.2
Browse files Browse the repository at this point in the history
  • Loading branch information
benmccallum committed Oct 25, 2021
2 parents 12c3bf4 + 56a633d commit e7a749c
Show file tree
Hide file tree
Showing 60 changed files with 1,494 additions and 429 deletions.
42 changes: 36 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Configure services.

```csharp
// Add your FluentValidation validators
// (note: this will add all validators in the assembly that contains `CreateUserInputValidator`)
services.AddValidatorsFromAssemblyContaining<CreateUserInputValidator>();

// Add FairyBread
Expand All @@ -37,17 +38,44 @@ Configure [FluentValidation](https://github.com/FluentValidation/FluentValidatio


```csharp
public class UserInput { ... }
public class CreateUserInput { ... }

public class UserInputValidator : AbstractValidator<CreateUserInput> { ... }
public class CreateUserInputValidator : AbstractValidator<CreateUserInput> { ... }

// An example GraphQL field in Hot Chocolate
public Task CreateUser(CreateUserInput userInput) { ... }
```

### How validation errors will be handled

Errors will be written out into the GraphQL execution result in the `Errors` property with one error being reported per failure on a field.
By default, errors will be written out into the GraphQL execution result in the `Errors` property with one error being reported per failure on a field.
You can change this behaviour by implementing your own `IValidationErrorsHandler`.

### Implicit vs explicit configuration

FairyBread opts for an implicit approach to validation by default, similar to how
[FluentValidation.AspNetCore](https://docs.fluentvalidation.net/en/latest/aspnet.html#asp-net-core)
behaves. Simply create a validator for a certain input object type and arguments of that type will be validated.
And you don't need to worry about a middleware performance penalty, as FairyBread (since v7.1) only adds the validation
field middleware where needed.

There are some cases though where explicitness is either required or useful, so you can do that too.

For example, if you've got a field argument that's a scalar type (e.g. not an input type), like an `int`, creating
a validator targeting `int` would mean every top-level `int` argument would be across your schema would be implicitly validated, which wouldn't make sense.
Instead, annotate the validator by having it inherit `IExplicitUsageOnly` and then explicitly setup it up on the argument (see below).

Annotation API:

* `[Validate(typeof(FooValidator)]` - explicitly add this validator for the argument
* `[DontValidate]` - don't validate this argument at all
* `[DontImplicitlyValidate]` - disable implicit validators for the argument

Fluent API:

* `.Argument("foo").ValidateWith<FooValidator>()`
* `.DontValidate()`
* `.DontImplicitlyValidate()`

### Dealing with multi-threaded execution issues

Expand All @@ -56,10 +84,12 @@ GraphQL resolvers are inherently multi-threaded; as such, you can run into issue
With FairyBread, you might need to do this if one of your validators uses a `DbContext` (say to check if a username already exists on a create user mutation). Good news is, it's as easy as marking your validator with `IRequiresOwnScopeValidator` and we'll take care of the rest.

```csharp
public class UserInputValidator : AbstractValidator<UserInput>, IRequiresOwnScopeValidator
public class CreateUserInputValidator
: AbstractValidator<CreateUserInput>
, IRequiresOwnScopeValidator
{
// db will be a unique instance for this validation operation
public UserInputValidator(SomeDbContext db) { ... }
public CreateUserInputValidator(SomeDbContext db) { ... }
}
```

Expand All @@ -68,7 +98,7 @@ public class UserInputValidator : AbstractValidator<UserInput>, IRequiresOwnScop
If you want to let MediatR fire validation, you can set up:
* FairyBread to skip validating `MediatR.IRequest` arguments,
* your MediatR pipeline to validate them and throw a `ValidationException`, and
* an `IErrorFilter`(in Hot Chocolate) to handle it handles using `FairyBread.DefaultValidationErrorsHandler` to report the errors.
* an `IErrorFilter`(in Hot Chocolate) to handle it using `FairyBread.DefaultValidationErrorsHandler` to report the errors.

### Where to next?

Expand Down
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<PackageIconUrl>https://github.com/benmccallum/fairybread/raw/master/logo-400x400.png</PackageIconUrl>
<PackageLicenseUrl>https://github.com/benmccallum/fairybread/blob/master/LICENSE</PackageLicenseUrl>

<Version>8.0.0-rc.4</Version>
<Version>8.0.0-rc.5</Version>

<HotChocolateVersion>12.0.1</HotChocolateVersion>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,19 @@
},
Extensions: {
code: FairyBread_ValidationError,
argumentName: foo,
validatorName: CustomValidator,
errorCode: GreaterThanOrEqualValidator,
errorMessage: 'Some Integer' must be greater than or equal to '999'.,
propertyName: SomeInteger,
attemptedValue: 1,
severity: Error,
formattedMessagePlaceholderValues: {
ComparisonValue: 999,
ComparisonProperty: ,
PropertyName: Some Integer,
PropertyValue: 1
}
},
propertyName: SomeInteger
}
}
]
Expand Down
4 changes: 2 additions & 2 deletions src/FairyBread.Tests/CustomizationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ public async Task CustomValidationResultHandler_Works()

public class CustomValidationErrorsHandler : DefaultValidationErrorsHandler
{
protected override IErrorBuilder CreateErrorBuilder(IMiddlewareContext context, ValidationFailure failure) =>
base.CreateErrorBuilder(context, failure)
protected override IErrorBuilder CreateErrorBuilder(IMiddlewareContext context, string argName, IValidator val, ValidationFailure failure) =>
base.CreateErrorBuilder(context, argName, val, failure)
.SetMessage("lol");
}

Expand Down
2 changes: 1 addition & 1 deletion src/FairyBread.Tests/DefaultValidatorRegistryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Xunit;
using static FairyBread.Tests.ValidationMiddlewareTests;
using static FairyBread.Tests.GeneralTests;

namespace FairyBread.Tests
{
Expand Down
2 changes: 1 addition & 1 deletion src/FairyBread.Tests/FairyBread.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="10.0.0" />
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="$(FluentValidationVersion)" />
<PackageReference Include="HotChocolate.Execution" Version="$(HotChocolateVersion)" />
<PackageReference Include="HotChocolate.Types.CursorPagination" Version="$(HotChocolateVersion)" />
<PackageReference Include="HotChocolate.Data" Version="$(HotChocolateVersion)" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,19 @@
},
Extensions: {
code: FairyBread_ValidationError,
argumentName: foo,
validatorName: FooInputDtoValidator,
errorCode: EqualValidator,
errorMessage: 'Some Integer' must be equal to '1'.,
propertyName: SomeInteger,
attemptedValue: -1,
severity: Error,
formattedMessagePlaceholderValues: {
ComparisonValue: 1,
ComparisonProperty: ,
PropertyName: Some Integer,
PropertyValue: -1
}
},
propertyName: SomeInteger
}
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,19 @@
},
Extensions: {
code: FairyBread_ValidationError,
argumentName: foo,
validatorName: FooInputDtoValidator,
errorCode: EqualValidator,
errorMessage: 'Some Integer' must be equal to '1'.,
propertyName: SomeInteger,
attemptedValue: -1,
severity: Error,
formattedMessagePlaceholderValues: {
ComparisonValue: 1,
ComparisonProperty: ,
PropertyName: Some Integer,
PropertyValue: -1
}
},
propertyName: SomeInteger
}
}
]
Expand All @@ -34,17 +36,19 @@
},
Extensions: {
code: FairyBread_ValidationError,
argumentName: foo,
validatorName: FooInputDtoValidator,
errorCode: EqualValidator,
errorMessage: 'Some Integer' must be equal to '1'.,
propertyName: SomeInteger,
attemptedValue: -1,
severity: Error,
formattedMessagePlaceholderValues: {
ComparisonValue: 1,
ComparisonProperty: ,
PropertyName: Some Integer,
PropertyValue: -1
}
},
propertyName: SomeInteger
}
}
]
Expand All @@ -59,17 +63,19 @@
},
Extensions: {
code: FairyBread_ValidationError,
argumentName: foo,
validatorName: FooInputDtoValidator,
errorCode: EqualValidator,
errorMessage: 'Some Integer' must be equal to '1'.,
propertyName: SomeInteger,
attemptedValue: -1,
severity: Error,
formattedMessagePlaceholderValues: {
ComparisonValue: 1,
ComparisonProperty: ,
PropertyName: Some Integer,
PropertyValue: -1
}
},
propertyName: SomeInteger
}
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,19 @@
},
Extensions: {
code: FairyBread_ValidationError,
argumentName: foo,
validatorName: FooInputDtoValidator,
errorCode: EqualValidator,
errorMessage: 'Some Integer' must be equal to '1'.,
propertyName: SomeInteger,
attemptedValue: -1,
severity: Error,
formattedMessagePlaceholderValues: {
ComparisonValue: 1,
ComparisonProperty: ,
PropertyName: Some Integer,
PropertyValue: -1
}
},
propertyName: SomeInteger
}
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@
},
Extensions: {
code: FairyBread_ValidationError,
argumentName: bar,
validatorName: BarInputDtoAsyncValidator,
errorCode: AsyncPredicateValidator,
errorMessage: The specified condition was not met for 'Email Address'.,
propertyName: EmailAddress,
attemptedValue: -1,
severity: Error,
formattedMessagePlaceholderValues: {
PropertyName: Email Address,
PropertyValue: -1
}
},
propertyName: EmailAddress
}
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,19 @@
},
Extensions: {
code: FairyBread_ValidationError,
argumentName: foo,
validatorName: FooInputDtoValidator,
errorCode: EqualValidator,
errorMessage: 'Some Integer' must be equal to '1'.,
propertyName: SomeInteger,
attemptedValue: -1,
severity: Error,
formattedMessagePlaceholderValues: {
ComparisonValue: 1,
ComparisonProperty: ,
PropertyName: Some Integer,
PropertyValue: -1
}
},
propertyName: SomeInteger
}
},
{
Expand All @@ -29,17 +31,19 @@
},
Extensions: {
code: FairyBread_ValidationError,
argumentName: foo,
validatorName: FooInputDtoValidator,
errorCode: EqualValidator,
errorMessage: 'Some String' must be equal to 'hello'.,
propertyName: SomeString,
attemptedValue: -1,
severity: Error,
formattedMessagePlaceholderValues: {
ComparisonValue: hello,
ComparisonProperty: ,
PropertyName: Some String,
PropertyValue: -1
}
},
propertyName: SomeString
}
},
{
Expand All @@ -50,15 +54,17 @@
},
Extensions: {
code: FairyBread_ValidationError,
argumentName: bar,
validatorName: BarInputDtoAsyncValidator,
errorCode: AsyncPredicateValidator,
errorMessage: The specified condition was not met for 'Email Address'.,
propertyName: EmailAddress,
attemptedValue: -1,
severity: Error,
formattedMessagePlaceholderValues: {
PropertyName: Email Address,
PropertyValue: -1
}
},
propertyName: EmailAddress
}
}
]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
Errors: [
{
Message: Unexpected Execution Error,
Path: {
Name: readWithArrayArg
},
Locations: [
{
Line: 1,
Column: 9
}
],
Exception: {
$type: InvalidOperationException,
Type: InvalidOperationException,
Message: Cannot validate instances of type 'List`1'. This validator can only validate instances of type 'FooInputDto[]'.
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
Errors: [
{
Message: Unexpected Execution Error,
Path: {
Name: readWithArrayArg
},
Locations: [
{
Line: 1,
Column: 9
}
],
Exception: {
$type: InvalidOperationException,
Type: InvalidOperationException,
Message: Cannot validate instances of type 'List`1'. This validator can only validate instances of type 'FooInputDto[]'.
}
}
]
}
Loading

0 comments on commit e7a749c

Please sign in to comment.