{"id":17182398,"url":"https://github.com/simoncropp/graphql.validation","last_synced_at":"2025-04-04T20:08:52.263Z","repository":{"id":34039212,"uuid":"165455736","full_name":"SimonCropp/GraphQL.Validation","owner":"SimonCropp","description":"Add FluentValidation support to GraphQL.net","archived":false,"fork":false,"pushed_at":"2025-04-03T05:39:04.000Z","size":1615,"stargazers_count":45,"open_issues_count":1,"forks_count":6,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-03T06:32:12.970Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SimonCropp.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"license.txt","code_of_conduct":"code_of_conduct.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"SimonCropp"}},"created_at":"2019-01-13T02:00:50.000Z","updated_at":"2025-04-03T05:39:08.000Z","dependencies_parsed_at":"2023-12-25T21:26:41.266Z","dependency_job_id":"26ab7604-a430-4fb7-890f-de8a3728c21c","html_url":"https://github.com/SimonCropp/GraphQL.Validation","commit_stats":{"total_commits":1500,"total_committers":10,"mean_commits":150.0,"dds":0.6026666666666667,"last_synced_commit":"f02d216a21a5bfbdcbc50d7bc61ede8d37f4bae2"},"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SimonCropp%2FGraphQL.Validation","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SimonCropp%2FGraphQL.Validation/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SimonCropp%2FGraphQL.Validation/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SimonCropp%2FGraphQL.Validation/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SimonCropp","download_url":"https://codeload.github.com/SimonCropp/GraphQL.Validation/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247242678,"owners_count":20907134,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-15T00:36:59.671Z","updated_at":"2025-04-04T20:08:52.225Z","avatar_url":"https://github.com/SimonCropp.png","language":"C#","funding_links":["https://github.com/sponsors/SimonCropp"],"categories":[],"sub_categories":[],"readme":"# \u003cimg src=\"/src/icon.png\" height=\"30px\"\u003e GraphQL.Validation\n\n[![Build status](https://ci.appveyor.com/api/projects/status/wvk8wm3n227b2b3q/branch/main?svg=true)](https://ci.appveyor.com/project/SimonCropp/graphql-validation)\n[![NuGet Status](https://img.shields.io/nuget/v/GraphQL.FluentValidation.svg)](https://www.nuget.org/packages/GraphQL.FluentValidation/)\n\nAdd [FluentValidation](https://fluentvalidation.net/) support to [GraphQL.net](https://github.com/graphql-dotnet/graphql-dotnet)\n\n**See [Milestones](../../milestones?state=closed) for release notes.**\n\n\n### Powered by\n\n[![JetBrains logo.](https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.svg)](https://jb.gg/OpenSourceSupport)\n\n\n## NuGet package\n\nhttps://nuget.org/packages/GraphQL.FluentValidation/\n\n\n## Usage\n\n\n### Define validators\n\nGiven the following input:\n\n\u003c!-- snippet: input --\u003e\n\u003ca id='snippet-input'\u003e\u003c/a\u003e\n```cs\npublic class MyInput\n{\n    public string Content { get; set; } = null!;\n}\n```\n\u003csup\u003e\u003ca href='/src/SampleWeb/Graphs/MyInput.cs#L1-L8' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-input' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\nAnd graph:\n\n\u003c!-- snippet: Graph --\u003e\n\u003ca id='snippet-Graph'\u003e\u003c/a\u003e\n```cs\npublic class MyInputGraph :\n    InputObjectGraphType\n{\n    public MyInputGraph() =\u003e\n        Field\u003cStringGraphType\u003e(\"content\");\n}\n```\n\u003csup\u003e\u003ca href='/src/SampleWeb/Graphs/MyInputGraph.cs#L3-L10' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-Graph' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\nA custom validator can be defined as follows:\n\n\u003c!-- snippet: validator --\u003e\n\u003ca id='snippet-validator'\u003e\u003c/a\u003e\n```cs\npublic class MyInputValidator :\n    AbstractValidator\u003cMyInput\u003e\n{\n    public MyInputValidator() =\u003e\n        RuleFor(_ =\u003e _.Content)\n            .NotEmpty();\n}\n```\n\u003csup\u003e\u003ca href='/src/SampleWeb/Graphs/MyInputValidator.cs#L3-L11' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-validator' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\n\n### Setup Validators\n\nValidators need to be added to the `ValidatorTypeCache`. This should be done once at application startup.\n\n\u003c!-- snippet: StartConfig --\u003e\n\u003ca id='snippet-StartConfig'\u003e\u003c/a\u003e\n```cs\nvar validatorCache = new ValidatorInstanceCache();\nvalidatorCache.AddValidatorsFromAssembly(assemblyContainingValidators);\nvar schema = new Schema();\nschema.UseFluentValidation();\nvar executer = new DocumentExecuter();\n```\n\u003csup\u003e\u003ca href='/src/Tests/Snippets/QueryExecution.cs#L16-L24' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-StartConfig' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\nGenerally `ValidatorTypeCache` is scoped per app and can be collocated with `Schema`, `DocumentExecuter` initialization.\n\nDependency Injection can be used for validators. Create a `ValidatorTypeCache` with the\n`useDependencyInjection: true` parameter and call one of the `AddValidatorsFrom*` methods from\n[FluentValidation.DependencyInjectionExtensions](https://www.nuget.org/packages/FluentValidation.DependencyInjectionExtensions/)\npackage in the `Startup`. By default, validators are added to the DI container with a transient lifetime.\n\n\n### Add to ExecutionOptions\n\nValidation needs to be added to any instance of `ExecutionOptions`.\n\n\u003c!-- snippet: UseFluentValidation --\u003e\n\u003ca id='snippet-UseFluentValidation'\u003e\u003c/a\u003e\n```cs\nvar options = new ExecutionOptions\n{\n    Schema = schema,\n    Query = queryString,\n    Variables = inputs\n};\noptions.UseFluentValidation(validatorCache);\n\nvar executionResult = await executer.ExecuteAsync(options);\n```\n\u003csup\u003e\u003ca href='/src/Tests/Snippets/QueryExecution.cs#L29-L41' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-UseFluentValidation' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\n\n### UserContext must be a dictionary\n\nThis library needs to be able to pass the list of validators, in the form of `ValidatorTypeCache`, through the graphql context. The only way of achieving this is to use the `ExecutionOptions.UserContext`. To facilitate this, the type passed to `ExecutionOptions.UserContext` has to implement `IDictionary\u003cstring, object\u003e`. There are two approaches to achieving this:\n\n\n#### 1. Have the user context class implement IDictionary\n\nGiven a user context class of the following form:\n\n\u003c!-- snippet: ContextImplementingDictionary --\u003e\n\u003ca id='snippet-ContextImplementingDictionary'\u003e\u003c/a\u003e\n```cs\npublic class MyUserContext(string myProperty) :\n    Dictionary\u003cstring, object?\u003e\n{\n    public string MyProperty { get; } = myProperty;\n}\n```\n\u003csup\u003e\u003ca href='/src/Tests/Snippets/QueryExecution.cs#L44-L52' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-ContextImplementingDictionary' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\nThe `ExecutionOptions.UserContext` can then be set as follows:\n\n\u003c!-- snippet: ExecuteQueryWithContextImplementingDictionary --\u003e\n\u003ca id='snippet-ExecuteQueryWithContextImplementingDictionary'\u003e\u003c/a\u003e\n```cs\nvar options = new ExecutionOptions\n{\n    Schema = schema,\n    Query = queryString,\n    Variables = inputs,\n    UserContext = new MyUserContext\n    (\n        myProperty: \"the value\"\n    )\n};\noptions.UseFluentValidation(validatorCache);\n```\n\u003csup\u003e\u003ca href='/src/Tests/Snippets/QueryExecution.cs#L56-L70' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-ExecuteQueryWithContextImplementingDictionary' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\n\n#### 2. Have the user context class exist inside a IDictionary\n\n\u003c!-- snippet: ExecuteQueryWithContextInsideDictionary --\u003e\n\u003ca id='snippet-ExecuteQueryWithContextInsideDictionary'\u003e\u003c/a\u003e\n```cs\nvar options = new ExecutionOptions\n{\n    Schema = schema,\n    Query = queryString,\n    Variables = inputs,\n    UserContext = new Dictionary\u003cstring, object?\u003e\n    {\n        {\n            \"MyUserContext\",\n            new MyUserContext\n            (\n                myProperty: \"the value\"\n            )\n        }\n    }\n};\noptions.UseFluentValidation(validatorCache);\n```\n\u003csup\u003e\u003ca href='/src/Tests/Snippets/QueryExecution.cs#L75-L95' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-ExecuteQueryWithContextInsideDictionary' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\n\n#### No UserContext\n\nIf no instance is passed to `ExecutionOptions.UserContext`:\n\n\u003c!-- snippet: NoContext --\u003e\n\u003ca id='snippet-NoContext'\u003e\u003c/a\u003e\n```cs\nvar options = new ExecutionOptions\n{\n    Schema = schema,\n    Query = queryString,\n    Variables = inputs\n};\noptions.UseFluentValidation(validatorCache);\n```\n\u003csup\u003e\u003ca href='/src/Tests/Snippets/QueryExecution.cs#L100-L110' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-NoContext' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\nThen the `UseFluentValidation` method will instantiate it to a new `Dictionary\u003cstring, object\u003e`.\n\n\n### Trigger validation\n\nTo trigger the validation, when reading arguments use `GetValidatedArgument` instead of `GetArgument`:\n\n\u003c!-- snippet: GetValidatedArgument --\u003e\n\u003ca id='snippet-GetValidatedArgument'\u003e\u003c/a\u003e\n```cs\npublic class Query :\n    ObjectGraphType\n{\n    public Query() =\u003e\n        Field\u003cResultGraph\u003e(\"inputQuery\")\n            .Argument\u003cMyInputGraph\u003e(\"input\")\n            .Resolve(context =\u003e\n                {\n                    var input = context.GetValidatedArgument\u003cMyInput\u003e(\"input\");\n                    return new Result\n                    {\n                        Data = input.Content\n                    };\n                }\n            );\n}\n```\n\u003csup\u003e\u003ca href='/src/SampleWeb/Query.cs#L4-L23' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-GetValidatedArgument' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\n\n### Difference from IValidationRule\n\nThe validation implemented in this project has nothing to do with the validation of the incoming GraphQL\nrequest, which is described in the [official specification](http://spec.graphql.org/June2018/#sec-Validation).\n[GraphQL.NET](https://github.com/graphql-dotnet/graphql-dotnet) has a concept of [validation rules](https://github.com/graphql-dotnet/graphql-dotnet/blob/master/src/GraphQL/Validation/IValidationRule.cs)\nthat would work **before** request execution stage. In this project validation occurs for input arguments\n**at the request execution stage**. This additional validation complements but does not replace the standard\nset of validation rules.\n\n\n## Testing\n\n### Integration\n\nA full end-to-en test can be run against the GraphQL controller:\n\n\u003c!-- snippet: GraphQLControllerTests --\u003e\n\u003ca id='snippet-GraphQLControllerTests'\u003e\u003c/a\u003e\n```cs\npublic class GraphQLControllerTests\n{\n    [Fact]\n    public async Task RunQuery()\n    {\n        using var server = GetTestServer();\n        using var client = server.CreateClient();\n        var query = \"\"\"\n                    {\n                      inputQuery(input: {content: \"TheContent\"}) {\n                        data\n                      }\n                    }\n                    \"\"\";\n        var body = new\n        {\n            query\n        };\n        var serialized = JsonConvert.SerializeObject(body);\n        using var content = new StringContent(\n            serialized,\n            Encoding.UTF8,\n            \"application/json\");\n        using var request = new HttpRequestMessage(HttpMethod.Post, \"graphql\")\n        {\n            Content = content\n        };\n        using var response = await client.SendAsync(request);\n        await Verify(response);\n    }\n\n    static TestServer GetTestServer()\n    {\n        var builder = new WebHostBuilder();\n        builder.UseStartup\u003cStartup\u003e();\n        return new(builder);\n    }\n}\n```\n\u003csup\u003e\u003ca href='/src/SampleWeb.Tests/GraphQLControllerTests.cs#L5-L46' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-GraphQLControllerTests' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\n\n### Unit\n\nUnit tests can be run a specific field of a query:\n\n\u003c!-- snippet: QueryTests --\u003e\n\u003ca id='snippet-QueryTests'\u003e\u003c/a\u003e\n```cs\npublic class QueryTests\n{\n    [Fact]\n    public async Task RunInputQuery()\n    {\n        var field = new Query().GetField(\"inputQuery\")!;\n\n        var userContext = new GraphQLUserContext();\n        FluentValidationExtensions.AddCacheToContext(\n            userContext,\n            ValidatorCacheBuilder.Instance);\n\n        var input = new MyInput\n        {\n            Content = \"TheContent\"\n        };\n        var fieldContext = new ResolveFieldContext\n        {\n            Arguments = new Dictionary\u003cstring, ArgumentValue\u003e\n            {\n                {\n                    \"input\", new(input, ArgumentSource.Variable)\n                }\n            },\n            UserContext = userContext\n        };\n        var result = await field.Resolver!.ResolveAsync(fieldContext);\n        await Verify(result);\n    }\n\n    [Fact]\n    public Task RunInvalidInputQuery()\n    {\n        Thread.CurrentThread.CurrentUICulture = new(\"en-US\");\n        var field = new Query().GetField(\"inputQuery\")!;\n\n        var userContext = new GraphQLUserContext();\n        FluentValidationExtensions.AddCacheToContext(\n            userContext,\n            ValidatorCacheBuilder.Instance);\n\n        var input = new MyInput\n        {\n            Content = null!\n        };\n        var fieldContext = new ResolveFieldContext\n        {\n            Arguments = new Dictionary\u003cstring, ArgumentValue\u003e\n            {\n                {\n                    \"input\", new(input, ArgumentSource.Variable)\n                }\n            },\n            UserContext = userContext\n        };\n        var exception = Assert.Throws\u003cValidationException\u003e(\n            () =\u003e field.Resolver!.ResolveAsync(fieldContext));\n        return Verify(exception.Message);\n    }\n}\n```\n\u003csup\u003e\u003ca href='/src/SampleWeb.Tests/QueryTests.cs#L5-L68' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-QueryTests' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\n\n## Icon\n\n[Shield](https://thenounproject.com/term/shield/1893182/) designed by [Maxim Kulikov](https://thenounproject.com/maxim221/) from [The Noun Project](https://thenounproject.com)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsimoncropp%2Fgraphql.validation","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsimoncropp%2Fgraphql.validation","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsimoncropp%2Fgraphql.validation/lists"}