├── icon.png
├── test
├── __snapshots__
│ ├── ValidationTests.Ensure_Validation_Works_On_Arguments_ValidEmail.snap
│ ├── ValidationTests.Ensure_Validation_Works_On_InputObjects_ValidEmail.snap
│ ├── ValidationTests.Ensure_Validation_Works_On_StudentWithScoreCard.snap
│ ├── ValidationTests.Ensure_Validation_Works_On_NonDuplicateEmail.snap
│ ├── ValidationTests.Ensure_Validation_Works_On_DuplicateEmail.snap
│ ├── ValidationTests.Ensure_Validation_Works_On_Arguments.snap
│ ├── ValidationTests.Ensure_Validation_Works_On_InputObjects.snap
│ └── ValidationTests.Ensure_Validation_Works_On_StudentWithOutScoreCard.snap
├── Graph.ArgumentValidator.Tests.csproj
└── ValidationTests.cs
├── src
├── WellKnownContextData.cs
├── ValidatableAttribute.cs
├── RequestExecutorBuilderExtensions.cs
├── Graph.ArgumentValidator.csproj
├── Directory.Build.props
├── ValidationMiddleware.cs
└── ValidationTypeInterceptor.cs
├── Sample
├── appsettings.Development.json
├── appsettings.json
├── Program.cs
├── Sample.csproj
└── Properties
│ └── launchSettings.json
├── Shared
├── MyInput.cs
├── Shared.csproj
├── DuplicateEmailValidatorService.cs
├── Student.cs
├── DuplicateEmailValidtorAttribute.cs
└── Query.cs
├── .vscode
└── tasks.json
├── LICENSE
├── .gitattributes
├── README.md
├── Graph.ArgumentValidator.sln
└── .gitignore
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VarunSaiTeja/Graph.ArgumentValidator/HEAD/icon.png
--------------------------------------------------------------------------------
/test/__snapshots__/ValidationTests.Ensure_Validation_Works_On_Arguments_ValidEmail.snap:
--------------------------------------------------------------------------------
1 | {
2 | "data": {
3 | "argIsEmail": "abc@abc.com"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/test/__snapshots__/ValidationTests.Ensure_Validation_Works_On_InputObjects_ValidEmail.snap:
--------------------------------------------------------------------------------
1 | {
2 | "data": {
3 | "argIsInput": "abc@abc.com"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/test/__snapshots__/ValidationTests.Ensure_Validation_Works_On_StudentWithScoreCard.snap:
--------------------------------------------------------------------------------
1 | {
2 | "data": {
3 | "checkPass": "Varun at Gayatri is passed"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/test/__snapshots__/ValidationTests.Ensure_Validation_Works_On_NonDuplicateEmail.snap:
--------------------------------------------------------------------------------
1 | {
2 | "data": {
3 | "checkDuplicateEmail": "You are good to go, this email not registred yet."
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src/WellKnownContextData.cs:
--------------------------------------------------------------------------------
1 | namespace Graph.ArgumentValidator
2 | {
3 | internal static class WellKnownContextData
4 | {
5 | public const string ValidationDelegate = nameof(ValidationDelegate);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/Sample/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Information"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Sample/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Information"
7 | }
8 | },
9 | "AllowedHosts": "*"
10 | }
11 |
--------------------------------------------------------------------------------
/Shared/MyInput.cs:
--------------------------------------------------------------------------------
1 | using Graph.ArgumentValidator;
2 | using System.ComponentModel.DataAnnotations;
3 |
4 | namespace Shared
5 | {
6 | [Validatable]
7 | public class MyInput
8 | {
9 | [EmailAddress, Required]
10 | public string Email { get; set; }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Shared/Shared.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/ValidatableAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Graph.ArgumentValidator
4 | {
5 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = true, AllowMultiple = false)]
6 | public class ValidatableAttribute : Attribute
7 | {
8 | public ValidatableAttribute()
9 | {
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Sample/Program.cs:
--------------------------------------------------------------------------------
1 | using Graph.ArgumentValidator;
2 | using Shared;
3 |
4 | var builder = WebApplication.CreateBuilder(args);
5 |
6 | builder.Services
7 | .AddGraphQLServer()
8 | .AddArgumentValidator()
9 | .AddQueryType();
10 | builder.Services
11 | .AddSingleton();
12 |
13 | var app = builder.Build();
14 |
15 | app.MapGraphQL();
16 |
17 | app.Run();
--------------------------------------------------------------------------------
/test/__snapshots__/ValidationTests.Ensure_Validation_Works_On_DuplicateEmail.snap:
--------------------------------------------------------------------------------
1 | {
2 | "errors": [
3 | {
4 | "message": "Email already exist",
5 | "locations": [
6 | {
7 | "line": 1,
8 | "column": 3
9 | }
10 | ],
11 | "path": [
12 | "email"
13 | ],
14 | "extensions": {
15 | "field": "email"
16 | }
17 | }
18 | ],
19 | "data": {
20 | "checkDuplicateEmail": null
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Sample/Sample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/test/__snapshots__/ValidationTests.Ensure_Validation_Works_On_Arguments.snap:
--------------------------------------------------------------------------------
1 | {
2 | "errors": [
3 | {
4 | "message": "The String field is not a valid e-mail address.",
5 | "locations": [
6 | {
7 | "line": 1,
8 | "column": 3
9 | }
10 | ],
11 | "path": [
12 | "email"
13 | ],
14 | "extensions": {
15 | "field": "email"
16 | }
17 | }
18 | ],
19 | "data": {
20 | "argIsEmail": null
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Shared/DuplicateEmailValidatorService.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Shared
4 | {
5 | public class DuplicateEmailValidatorService
6 | {
7 | public bool IsEmailExist(string newEmail)
8 | {
9 | var existingEmails = new List
10 | {
11 | "varun@gmail.com",
12 | "teja@gmail.com"
13 | };
14 |
15 | return !existingEmails.Contains(newEmail);
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/test/__snapshots__/ValidationTests.Ensure_Validation_Works_On_InputObjects.snap:
--------------------------------------------------------------------------------
1 | {
2 | "errors": [
3 | {
4 | "message": "The Email field is not a valid e-mail address.",
5 | "locations": [
6 | {
7 | "line": 1,
8 | "column": 3
9 | }
10 | ],
11 | "path": [
12 | "input"
13 | ],
14 | "extensions": {
15 | "field": "email"
16 | }
17 | }
18 | ],
19 | "data": {
20 | "argIsInput": null
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/test/__snapshots__/ValidationTests.Ensure_Validation_Works_On_StudentWithOutScoreCard.snap:
--------------------------------------------------------------------------------
1 | {
2 | "errors": [
3 | {
4 | "message": "`scoreCard` is a required field and cannot be null.",
5 | "locations": [
6 | {
7 | "line": 1,
8 | "column": 26
9 | }
10 | ],
11 | "path": [
12 | "checkPass"
13 | ],
14 | "extensions": {
15 | "field": "scoreCard",
16 | "specifiedBy": "https://spec.graphql.org/October2021/#sec-Input-Object-Required-Fields"
17 | }
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/src/RequestExecutorBuilderExtensions.cs:
--------------------------------------------------------------------------------
1 | using HotChocolate.Execution.Configuration;
2 | using Microsoft.Extensions.DependencyInjection;
3 |
4 | namespace Graph.ArgumentValidator
5 | {
6 | public static class RequestExecutorBuilderExtensions
7 | {
8 | public static IRequestExecutorBuilder AddArgumentValidator(
9 | this IRequestExecutorBuilder requestExecutorBuilder)
10 | {
11 | requestExecutorBuilder.TryAddTypeInterceptor();
12 |
13 | return requestExecutorBuilder;
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Shared/Student.cs:
--------------------------------------------------------------------------------
1 | using Graph.ArgumentValidator;
2 | using System.ComponentModel.DataAnnotations;
3 |
4 | namespace Shared
5 | {
6 | [Validatable]
7 | public class Student
8 | {
9 | [Required(AllowEmptyStrings = false)]
10 | public string FirstName { get; set; }
11 | [Required]
12 | public ScoreCardInfo ScoreCard { get; set; }
13 |
14 | public class ScoreCardInfo
15 | {
16 | [Required]
17 | public int TotalScore { get; set; }
18 |
19 | [Required(AllowEmptyStrings = false)]
20 | public string School { get; set; }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Shared/DuplicateEmailValidtorAttribute.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 |
3 | namespace Shared
4 | {
5 | public class DuplicateEmailValidtorAttribute : ValidationAttribute
6 | {
7 | protected override ValidationResult IsValid(object valueObj, ValidationContext validationContext)
8 | {
9 | var value = valueObj as string;
10 | var service = (DuplicateEmailValidatorService)validationContext.GetService(typeof(DuplicateEmailValidatorService));
11 | return service.IsEmailExist(value) ? ValidationResult.Success : new ValidationResult("Email already exist");
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // See https://go.microsoft.com/fwlink/?LinkId=733558
3 | // for the documentation about the tasks.json format
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "label": "build",
8 | "command": "dotnet",
9 | "type": "shell",
10 | "args": [
11 | "build",
12 | // Ask dotnet build to generate full paths for file names.
13 | "/property:GenerateFullPaths=true",
14 | // Do not generate summary otherwise it leads to duplicate errors in Problems panel
15 | "/consoleloggerparameters:NoSummary"
16 | ],
17 | "group": "build",
18 | "presentation": {
19 | "reveal": "silent"
20 | },
21 | "problemMatcher": "$msCompile"
22 | }
23 | ]
24 | }
--------------------------------------------------------------------------------
/src/Graph.ArgumentValidator.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | net8.0; net6.0
6 | net8.0; net6.0; netstandard2.0
7 | HotChocolate.Validator, HotChocolate, GraphQL
8 | icon.png
9 | true
10 | true
11 | $(Version)
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | True
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Sample/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/launchsettings.json",
3 | "iisSettings": {
4 | "windowsAuthentication": false,
5 | "anonymousAuthentication": true,
6 | "iisExpress": {
7 | "applicationUrl": "http://localhost:23573",
8 | "sslPort": 44334
9 | }
10 | },
11 | "profiles": {
12 | "IIS Express": {
13 | "commandName": "IISExpress",
14 | "launchBrowser": true,
15 | "launchUrl": "graphql",
16 | "environmentVariables": {
17 | "ASPNETCORE_ENVIRONMENT": "Development"
18 | }
19 | },
20 | "Sample": {
21 | "commandName": "Project",
22 | "launchBrowser": true,
23 | "launchUrl": "weatherforecast",
24 | "applicationUrl": "https://localhost:5001;http://localhost:5000",
25 | "environmentVariables": {
26 | "ASPNETCORE_ENVIRONMENT": "Development"
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | latest
4 | https://github.com/VarunSaiTeja/Graph.ArgumentValidator
5 | https://github.com/VarunSaiTeja/Graph.ArgumentValidator
6 | git
7 | Varun Teja
8 | Varun Teja
9 | $(MSBuildProjectName)
10 | Input Argument Validator for HotChocolate
11 | $(MSBuildProjectName)
12 | $(MSBuildProjectName)
13 | $(MSBuildProjectName)
14 | latest
15 |
16 | $(MSBuildProjectName)
17 | MIT
18 |
19 | 4.0.0
20 |
21 |
22 |
--------------------------------------------------------------------------------
/Shared/Query.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 |
3 | namespace Shared
4 | {
5 | public class Query
6 | {
7 | public string ArgIsEmail([EmailAddress, Required] string email) => email;
8 |
9 | ///
10 | /// Gives validation failed result if email already exist. Other wise *You are good to go...*
11 | ///
12 | ///
13 | ///
14 | public string CheckDuplicateEmail([EmailAddress, Required][DuplicateEmailValidtor] string email) => "You are good to go, this email not registred yet.";
15 |
16 | public string ArgIsInput(MyInput input) => input.Email;
17 |
18 | public string CheckPass(Student student)
19 | {
20 | if (student.ScoreCard.TotalScore > 30)
21 | return $"{student.FirstName} at {student.ScoreCard.School} is passed";
22 | else
23 | return $"{student.FirstName} at {student.ScoreCard.School} is failed";
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/test/Graph.ArgumentValidator.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 |
6 | false
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | runtime; build; native; contentfiles; analyzers; buildtransitive
18 | all
19 |
20 |
21 | runtime; build; native; contentfiles; analyzers; buildtransitive
22 | all
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Microsoft Corporation.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE
22 |
--------------------------------------------------------------------------------
/src/ValidationMiddleware.cs:
--------------------------------------------------------------------------------
1 | using HotChocolate;
2 | using HotChocolate.Resolvers;
3 | using System.Collections.Generic;
4 | using System.ComponentModel.DataAnnotations;
5 | using System.Linq;
6 | using System.Threading.Tasks;
7 | using HotChocolate.Execution;
8 |
9 | namespace Graph.ArgumentValidator
10 | {
11 | internal class ValidationMiddleware
12 | {
13 | private readonly FieldDelegate _next;
14 |
15 | public ValidationMiddleware(FieldDelegate next)
16 | {
17 | _next = next;
18 | }
19 |
20 | // this middleware is ensured to only execute on fields that have arguments that need validation.
21 | public async Task InvokeAsync(IMiddlewareContext context)
22 | {
23 | var errors = new List();
24 | var hasErrors = false;
25 |
26 | // we could even further optimize and aggregate this list in the interceptor and inject it into the middleware
27 | foreach (var argument in context.Selection.Field.Arguments)
28 | {
29 | if (argument.ContextData.TryGetValue(WellKnownContextData.ValidationDelegate, out object value) &&
30 | value is Validate validate)
31 | {
32 | var input = context.ArgumentValue