├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── config.yml
│ └── feature_request.md
├── dependabot.yml
├── stale.yml
└── workflows
│ ├── merge-dependabot.yml
│ └── on-push-do-docs.yml
├── .gitignore
├── code_of_conduct.md
├── license.txt
├── readme.md
└── src
├── .editorconfig
├── .gitattributes
├── Directory.Build.props
├── Directory.Packages.props
├── GraphQL.FluentValidation
├── ArgumentTypeCacheBag.cs
├── ArgumentValidation.cs
├── FluentValidationExtensions.cs
├── FluentValidationExtensions_GetArgument.cs
├── FluentValidationExtensions_GetArgumentAsync.cs
├── FluentValidationExtensions_UserContext.cs
├── GlobalUsings.cs
├── GraphQL.FluentValidation.csproj
├── IValidatorCache.cs
├── InternalsVisibleTo.cs
├── ModuleInitializer.cs
├── TypeComparer.cs
├── ValidationMiddleware.cs
├── ValidatorCacheExtensions.cs
├── ValidatorInstanceCache.cs
└── ValidatorServiceCache.cs
├── GraphQL.Validation.sln
├── GraphQL.Validation.sln.DotSettings
├── SampleWeb.Tests
├── GraphQLControllerTests.RunQuery.verified.txt
├── GraphQLControllerTests.cs
├── ModuleInitializer.cs
├── QueryTests.RunInputQuery.verified.txt
├── QueryTests.RunInvalidInputQuery.verified.txt
├── QueryTests.cs
└── SampleWeb.Tests.csproj
├── SampleWeb
├── GraphQlUserContext.cs
├── Graphs
│ ├── MyInput.cs
│ ├── MyInputGraph.cs
│ ├── MyInputValidator.cs
│ ├── Result.cs
│ └── ResultGraph.cs
├── Program.cs
├── Properties
│ └── launchSettings.json
├── Query.cs
├── SampleWeb.csproj
├── Schema.cs
├── Startup.cs
└── ValidatorCacheBuilder.cs
├── Shared.sln.DotSettings
├── Tests
├── Arguments
│ ├── AsyncComplexInput.cs
│ ├── AsyncComplexInputGraph.cs
│ ├── AsyncComplexInputValidator.cs
│ ├── AsyncInput.cs
│ ├── AsyncInputGraph.cs
│ ├── AsyncInputValidator.cs
│ ├── ComplexInput.cs
│ ├── ComplexInputGraph.cs
│ ├── ComplexInputInner.cs
│ ├── ComplexInputInnerGraph.cs
│ ├── ComplexInputInnerValidator.cs
│ ├── ComplexInputListItem.cs
│ ├── ComplexInputListItemGraph.cs
│ ├── ComplexInputListItemValidator.cs
│ ├── ComplexInputValidator.cs
│ ├── DerivedComplexInput.cs
│ ├── DerivedComplexInputGraph.cs
│ ├── Input.cs
│ ├── InputGraph.cs
│ ├── InputValidator.cs
│ ├── NoEmptyConstructorValidator.cs
│ ├── NoValidatorInput.cs
│ ├── NoValidatorInputGraph.cs
│ └── NoValidatorInputValidator.cs
├── GlobalUsings.cs
├── IntegrationTests.AsyncComplexInvalid.verified.txt
├── IntegrationTests.AsyncComplexValid.verified.txt
├── IntegrationTests.AsyncInvalid.verified.txt
├── IntegrationTests.AsyncValid.verified.txt
├── IntegrationTests.ComplexInvalid.verified.txt
├── IntegrationTests.ComplexInvalid2.verified.txt
├── IntegrationTests.ComplexValid.verified.txt
├── IntegrationTests.DerivedComplexInvalid.verified.txt
├── IntegrationTests.GetCurrentValidators.verified.txt
├── IntegrationTests.Invalid.verified.txt
├── IntegrationTests.NoValidatorInvalid.verified.txt
├── IntegrationTests.NoValidatorValid.verified.txt
├── IntegrationTests.Valid.verified.txt
├── IntegrationTests.ValidNull.verified.txt
├── IntegrationTests.cs
├── ModuleInitializer.cs
├── Query.cs
├── QueryExecutor.cs
├── Result.cs
├── ResultGraph.cs
├── Schema.cs
├── Snippets
│ └── QueryExecution.cs
├── TestConfig.cs
└── Tests.csproj
├── appveyor.yml
├── global.json
├── icon.png
├── key.snk
├── mdsnippets.json
└── nuget.config
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: SimonCropp
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug fix
3 | about: Create a bug fix to help us improve
4 | ---
5 |
6 | Note: New issues raised, where it is clear the submitter has not read the issue template, are likely to be closed with "please read the issue template". Please don't take offense at this. It is simply a time management decision. If someone raises an issue, and can't be bothered to spend the time to read the issue template, then the project maintainers should not be expected to spend the time to read the submitted issue. Often too much time is spent going back and forth in issue comments asking for information that is outlined in the issue template.
7 |
8 | #### Preamble
9 |
10 | General questions may be better placed [StackOveflow](https://stackoverflow.com/).
11 |
12 | Where relevant, ensure you are using the current stable versions on your development stack. For example:
13 |
14 | * Visual Studio
15 | * [.NET SDK or .NET Core SDK](https://www.microsoft.com/net/download)
16 | * Any related NuGet packages
17 |
18 | Any code or stack traces must be properly formatted with [GitHub markdown](https://guides.github.com/features/mastering-markdown/).
19 |
20 | #### Describe the bug
21 |
22 | A clear and concise description of what the bug is. Include any relevant version information.
23 |
24 | A clear and concise description of what you expected to happen.
25 |
26 | Add any other context about the problem here.
27 |
28 | #### Minimal Repro
29 |
30 | Ensure you have replicated the bug in a minimal solution with the fewest moving parts. Often this will help point to the true cause of the problem. Upload this repro as part of the issue, preferably a public GitHub repository or a downloadable zip. The repro will allow the maintainers of this project to smoke test the any fix.
31 |
32 | #### Submit a PR that fixes the bug
33 |
34 | Submit a [Pull Request (PR)](https://help.github.com/articles/about-pull-requests/) that fixes the bug. Include in this PR a test that verifies the fix. If you were not able to fix the bug, a PR that illustrates your partial progress will suffice.
35 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: How to raise feature requests
4 | ---
5 |
6 | Note: New issues raised, where it is clear the submitter has not read the issue template, are likely to be closed with "please read the issue template". Please don't take offense at this. It is simply a time management decision. If someone raises an issue, and can't be bothered to spend the time to read the issue template, then the project maintainers should not be expected to spend the time to read the submitted issue. Often too much time is spent going back and forth in issue comments asking for information that is outlined in the issue template.
7 |
8 | If you are certain the feature will be accepted, it is better to raise a [Pull Request (PR)](https://help.github.com/articles/about-pull-requests/).
9 |
10 | If you are uncertain if the feature will be accepted, outline the proposal below to confirm it is viable, prior to raising a PR that implements the feature.
11 |
12 | Note that even if the feature is a good idea and viable, it may not be accepted since the ongoing effort in maintaining the feature may outweigh the benefit it delivers.
13 |
14 |
15 | #### Is the feature request related to a problem
16 |
17 | A clear and concise description of what the problem is.
18 |
19 | #### Describe the solution
20 |
21 | A clear and concise proposal of how you intend to implement the feature.
22 |
23 | #### Describe alternatives considered
24 |
25 | A clear and concise description of any alternative solutions or features you've considered.
26 |
27 | #### Additional context
28 |
29 | Add any other context about the feature request here.
30 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: nuget
4 | directory: "/src"
5 | schedule:
6 | interval: daily
7 | open-pull-requests-limit: 10
8 |
--------------------------------------------------------------------------------
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # Number of days of inactivity before an issue becomes stale
2 | daysUntilStale: 7
3 | # Number of days of inactivity before a stale issue is closed
4 | daysUntilClose: 7
5 | # Set to true to ignore issues in a milestone (defaults to false)
6 | exemptMilestones: true
7 | # Comment to post when marking an issue as stale. Set to `false` to disable
8 | markComment: >
9 | This issue has been automatically marked as stale because it has not had
10 | recent activity. It will be closed if no further activity occurs. Thank you
11 | for your contributions.
12 | # Comment to post when closing a stale issue. Set to `false` to disable
13 | closeComment: false
14 | # Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
15 | pulls:
16 | daysUntilStale: 30
17 | exemptLabels:
18 | - Question
19 | - Bug
20 | - Feature
21 | - Improvement
--------------------------------------------------------------------------------
/.github/workflows/merge-dependabot.yml:
--------------------------------------------------------------------------------
1 | name: merge-dependabot
2 | on:
3 | pull_request:
4 | jobs:
5 | automerge:
6 | runs-on: ubuntu-latest
7 | if: github.actor == 'dependabot[bot]'
8 | steps:
9 | - name: Dependabot Auto Merge
10 | uses: ahmadnassri/action-dependabot-auto-merge@v2.6.6
11 | with:
12 | target: minor
13 | github-token: ${{ secrets.dependabot }}
14 | command: squash and merge
--------------------------------------------------------------------------------
/.github/workflows/on-push-do-docs.yml:
--------------------------------------------------------------------------------
1 | name: on-push-do-docs
2 | on:
3 | push:
4 | jobs:
5 | docs:
6 | runs-on: windows-latest
7 | steps:
8 | - uses: actions/checkout@v4
9 | - name: Run MarkdownSnippets
10 | run: |
11 | dotnet tool install --global MarkdownSnippets.Tool
12 | mdsnippets ${GITHUB_WORKSPACE}
13 | shell: bash
14 | - name: Push changes
15 | run: |
16 | git config --local user.email "action@github.com"
17 | git config --local user.name "GitHub Action"
18 | git commit -m "Docs changes" -a || echo "nothing to commit"
19 | remote="https://${GITHUB_ACTOR}:${{secrets.GITHUB_TOKEN}}@github.com/${GITHUB_REPOSITORY}.git"
20 | branch="${GITHUB_REF:11}"
21 | git push "${remote}" ${branch} || echo "nothing to push"
22 | shell: bash
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.suo
2 | *.user
3 | bin/
4 | obj/
5 | .vs/
6 | *.DotSettings.user
7 | .idea/
8 | *.received.*
9 | nugets/
--------------------------------------------------------------------------------
/code_of_conduct.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at simon.cropp@gmail.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/license.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Simon Cropp
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 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | #
GraphQL.Validation
2 |
3 | [](https://ci.appveyor.com/project/SimonCropp/graphql-validation)
4 | [](https://www.nuget.org/packages/GraphQL.FluentValidation/)
5 |
6 | Add [FluentValidation](https://fluentvalidation.net/) support to [GraphQL.net](https://github.com/graphql-dotnet/graphql-dotnet)
7 |
8 | **See [Milestones](../../milestones?state=closed) for release notes.**
9 |
10 |
11 | ### Powered by
12 |
13 | [](https://jb.gg/OpenSourceSupport)
14 |
15 |
16 | ## NuGet package
17 |
18 | https://nuget.org/packages/GraphQL.FluentValidation/
19 |
20 |
21 | ## Usage
22 |
23 |
24 | ### Define validators
25 |
26 | Given the following input:
27 |
28 |
29 |
30 | ```cs
31 | public class MyInput
32 | {
33 | public string Content { get; set; } = null!;
34 | }
35 | ```
36 | snippet source | anchor
37 |
38 |
39 | And graph:
40 |
41 |
42 |
43 | ```cs
44 | public class MyInputGraph :
45 | InputObjectGraphType
46 | {
47 | public MyInputGraph() =>
48 | Field("content");
49 | }
50 | ```
51 | snippet source | anchor
52 |
53 |
54 | A custom validator can be defined as follows:
55 |
56 |
57 |
58 | ```cs
59 | public class MyInputValidator :
60 | AbstractValidator
61 | {
62 | public MyInputValidator() =>
63 | RuleFor(_ => _.Content)
64 | .NotEmpty();
65 | }
66 | ```
67 | snippet source | anchor
68 |
69 |
70 |
71 | ### Setup Validators
72 |
73 | Validators need to be added to the `ValidatorTypeCache`. This should be done once at application startup.
74 |
75 |
76 |
77 | ```cs
78 | var validatorCache = new ValidatorInstanceCache();
79 | validatorCache.AddValidatorsFromAssembly(assemblyContainingValidators);
80 | var schema = new Schema();
81 | schema.UseFluentValidation();
82 | var executer = new DocumentExecuter();
83 | ```
84 | snippet source | anchor
85 |
86 |
87 | Generally `ValidatorTypeCache` is scoped per app and can be collocated with `Schema`, `DocumentExecuter` initialization.
88 |
89 | Dependency Injection can be used for validators. Create a `ValidatorTypeCache` with the
90 | `useDependencyInjection: true` parameter and call one of the `AddValidatorsFrom*` methods from
91 | [FluentValidation.DependencyInjectionExtensions](https://www.nuget.org/packages/FluentValidation.DependencyInjectionExtensions/)
92 | package in the `Startup`. By default, validators are added to the DI container with a transient lifetime.
93 |
94 |
95 | ### Add to ExecutionOptions
96 |
97 | Validation needs to be added to any instance of `ExecutionOptions`.
98 |
99 |
100 |
101 | ```cs
102 | var options = new ExecutionOptions
103 | {
104 | Schema = schema,
105 | Query = queryString,
106 | Variables = inputs
107 | };
108 | options.UseFluentValidation(validatorCache);
109 |
110 | var executionResult = await executer.ExecuteAsync(options);
111 | ```
112 | snippet source | anchor
113 |
114 |
115 |
116 | ### UserContext must be a dictionary
117 |
118 | This 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`. There are two approaches to achieving this:
119 |
120 |
121 | #### 1. Have the user context class implement IDictionary
122 |
123 | Given a user context class of the following form:
124 |
125 |
126 |
127 | ```cs
128 | public class MyUserContext(string myProperty) :
129 | Dictionary
130 | {
131 | public string MyProperty { get; } = myProperty;
132 | }
133 | ```
134 | snippet source | anchor
135 |
136 |
137 | The `ExecutionOptions.UserContext` can then be set as follows:
138 |
139 |
140 |
141 | ```cs
142 | var options = new ExecutionOptions
143 | {
144 | Schema = schema,
145 | Query = queryString,
146 | Variables = inputs,
147 | UserContext = new MyUserContext
148 | (
149 | myProperty: "the value"
150 | )
151 | };
152 | options.UseFluentValidation(validatorCache);
153 | ```
154 | snippet source | anchor
155 |
156 |
157 |
158 | #### 2. Have the user context class exist inside a IDictionary
159 |
160 |
161 |
162 | ```cs
163 | var options = new ExecutionOptions
164 | {
165 | Schema = schema,
166 | Query = queryString,
167 | Variables = inputs,
168 | UserContext = new Dictionary
169 | {
170 | {
171 | "MyUserContext",
172 | new MyUserContext
173 | (
174 | myProperty: "the value"
175 | )
176 | }
177 | }
178 | };
179 | options.UseFluentValidation(validatorCache);
180 | ```
181 | snippet source | anchor
182 |
183 |
184 |
185 | #### No UserContext
186 |
187 | If no instance is passed to `ExecutionOptions.UserContext`:
188 |
189 |
190 |
191 | ```cs
192 | var options = new ExecutionOptions
193 | {
194 | Schema = schema,
195 | Query = queryString,
196 | Variables = inputs
197 | };
198 | options.UseFluentValidation(validatorCache);
199 | ```
200 | snippet source | anchor
201 |
202 |
203 | Then the `UseFluentValidation` method will instantiate it to a new `Dictionary`.
204 |
205 |
206 | ### Trigger validation
207 |
208 | To trigger the validation, when reading arguments use `GetValidatedArgument` instead of `GetArgument`:
209 |
210 |
211 |
212 | ```cs
213 | public class Query :
214 | ObjectGraphType
215 | {
216 | public Query() =>
217 | Field("inputQuery")
218 | .Argument("input")
219 | .Resolve(context =>
220 | {
221 | var input = context.GetValidatedArgument("input");
222 | return new Result
223 | {
224 | Data = input.Content
225 | };
226 | }
227 | );
228 | }
229 | ```
230 | snippet source | anchor
231 |
232 |
233 |
234 | ### Difference from IValidationRule
235 |
236 | The validation implemented in this project has nothing to do with the validation of the incoming GraphQL
237 | request, which is described in the [official specification](http://spec.graphql.org/June2018/#sec-Validation).
238 | [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)
239 | that would work **before** request execution stage. In this project validation occurs for input arguments
240 | **at the request execution stage**. This additional validation complements but does not replace the standard
241 | set of validation rules.
242 |
243 |
244 | ## Testing
245 |
246 | ### Integration
247 |
248 | A full end-to-en test can be run against the GraphQL controller:
249 |
250 |
251 |
252 | ```cs
253 | public class GraphQLControllerTests
254 | {
255 | [Fact]
256 | public async Task RunQuery()
257 | {
258 | using var server = GetTestServer();
259 | using var client = server.CreateClient();
260 | var query = """
261 | {
262 | inputQuery(input: {content: "TheContent"}) {
263 | data
264 | }
265 | }
266 | """;
267 | var body = new
268 | {
269 | query
270 | };
271 | var serialized = JsonConvert.SerializeObject(body);
272 | using var content = new StringContent(
273 | serialized,
274 | Encoding.UTF8,
275 | "application/json");
276 | using var request = new HttpRequestMessage(HttpMethod.Post, "graphql")
277 | {
278 | Content = content
279 | };
280 | using var response = await client.SendAsync(request);
281 | await Verify(response);
282 | }
283 |
284 | static TestServer GetTestServer()
285 | {
286 | var builder = new WebHostBuilder();
287 | builder.UseStartup();
288 | return new(builder);
289 | }
290 | }
291 | ```
292 | snippet source | anchor
293 |
294 |
295 |
296 | ### Unit
297 |
298 | Unit tests can be run a specific field of a query:
299 |
300 |
301 |
302 | ```cs
303 | public class QueryTests
304 | {
305 | [Fact]
306 | public async Task RunInputQuery()
307 | {
308 | var field = new Query().GetField("inputQuery")!;
309 |
310 | var userContext = new GraphQLUserContext();
311 | FluentValidationExtensions.AddCacheToContext(
312 | userContext,
313 | ValidatorCacheBuilder.Instance);
314 |
315 | var input = new MyInput
316 | {
317 | Content = "TheContent"
318 | };
319 | var fieldContext = new ResolveFieldContext
320 | {
321 | Arguments = new Dictionary
322 | {
323 | {
324 | "input", new(input, ArgumentSource.Variable)
325 | }
326 | },
327 | UserContext = userContext
328 | };
329 | var result = await field.Resolver!.ResolveAsync(fieldContext);
330 | await Verify(result);
331 | }
332 |
333 | [Fact]
334 | public Task RunInvalidInputQuery()
335 | {
336 | Thread.CurrentThread.CurrentUICulture = new("en-US");
337 | var field = new Query().GetField("inputQuery")!;
338 |
339 | var userContext = new GraphQLUserContext();
340 | FluentValidationExtensions.AddCacheToContext(
341 | userContext,
342 | ValidatorCacheBuilder.Instance);
343 |
344 | var input = new MyInput
345 | {
346 | Content = null!
347 | };
348 | var fieldContext = new ResolveFieldContext
349 | {
350 | Arguments = new Dictionary
351 | {
352 | {
353 | "input", new(input, ArgumentSource.Variable)
354 | }
355 | },
356 | UserContext = userContext
357 | };
358 | var exception = Assert.Throws(
359 | () => field.Resolver!.ResolveAsync(fieldContext));
360 | return Verify(exception.Message);
361 | }
362 | }
363 | ```
364 | snippet source | anchor
365 |
366 |
367 |
368 | ## Icon
369 |
370 | [Shield](https://thenounproject.com/term/shield/1893182/) designed by [Maxim Kulikov](https://thenounproject.com/maxim221/) from [The Noun Project](https://thenounproject.com)
371 |
--------------------------------------------------------------------------------
/src/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 |
6 | [*.cs]
7 | indent_size = 4
8 | charset = utf-8
9 |
10 | # Redundant accessor body
11 | resharper_redundant_accessor_body_highlighting = error
12 |
13 | # Replace with field keyword
14 | resharper_replace_with_field_keyword_highlighting = error
15 |
16 | # Replace with single call to Single(..)
17 | resharper_replace_with_single_call_to_single_highlighting = error
18 |
19 | # Replace with single call to SingleOrDefault(..)
20 | resharper_replace_with_single_call_to_single_or_default_highlighting = error
21 |
22 | # Replace with single call to LastOrDefault(..)
23 | resharper_replace_with_single_call_to_last_or_default_highlighting = error
24 |
25 | # Replace with single call to Last(..)
26 | resharper_replace_with_single_call_to_last_highlighting = error
27 |
28 | # Replace with single call to First(..)
29 | resharper_replace_with_single_call_to_first_highlighting = error
30 |
31 | # Replace with single call to FirstOrDefault(..)
32 | resharper_replace_with_single_call_to_first_or_default_highlighting = error
33 |
34 | # Replace with single call to Any(..)
35 | resharper_replace_with_single_call_to_any_highlighting = error
36 |
37 | # Replace with single call to Count(..)
38 | resharper_replace_with_single_call_to_count_highlighting = error
39 |
40 | # Declare types in namespaces
41 | dotnet_diagnostic.CA1050.severity = none
42 |
43 | # Use Literals Where Appropriate
44 | dotnet_diagnostic.CA1802.severity = error
45 |
46 | # Template should be a static expression
47 | dotnet_diagnostic.CA2254.severity = error
48 |
49 | # Potentially misleading parameter name in lambda or local function
50 | resharper_all_underscore_local_parameter_name_highlighting = none
51 |
52 | # Redundant explicit collection creation in argument of 'params' parameter
53 | resharper_redundant_explicit_params_array_creation_highlighting = error
54 |
55 | # Do not initialize unnecessarily
56 | dotnet_diagnostic.CA1805.severity = error
57 |
58 | # Avoid unsealed attributes
59 | dotnet_diagnostic.CA1813.severity = error
60 |
61 | # Test for empty strings using string length
62 | dotnet_diagnostic.CA1820.severity = none
63 |
64 | # Remove empty finalizers
65 | dotnet_diagnostic.CA1821.severity = error
66 |
67 | # Mark members as static
68 | dotnet_diagnostic.CA1822.severity = error
69 |
70 | # Avoid unused private fields
71 | dotnet_diagnostic.CA1823.severity = error
72 |
73 | # Avoid zero-length array allocations
74 | dotnet_diagnostic.CA1825.severity = error
75 |
76 | # Use property instead of Linq Enumerable method
77 | dotnet_diagnostic.CA1826.severity = error
78 |
79 | # Do not use Count()/LongCount() when Any() can be used
80 | dotnet_diagnostic.CA1827.severity = error
81 | dotnet_diagnostic.CA1828.severity = error
82 |
83 | # Use Length/Count property instead of Enumerable.Count method
84 | dotnet_diagnostic.CA1829.severity = error
85 |
86 | # Prefer strongly-typed Append and Insert method overloads on StringBuilder
87 | dotnet_diagnostic.CA1830.severity = error
88 |
89 | # Use AsSpan instead of Range-based indexers for string when appropriate
90 | dotnet_diagnostic.CA1831.severity = error
91 |
92 | # Use AsSpan instead of Range-based indexers for string when appropriate
93 | dotnet_diagnostic.CA1831.severity = error
94 | dotnet_diagnostic.CA1832.severity = error
95 | dotnet_diagnostic.CA1833.severity = error
96 |
97 | # Use StringBuilder.Append(char) for single character strings
98 | dotnet_diagnostic.CA1834.severity = error
99 |
100 | # Prefer IsEmpty over Count when available
101 | dotnet_diagnostic.CA1836.severity = error
102 |
103 | # Prefer IsEmpty over Count when available
104 | dotnet_diagnostic.CA1836.severity = error
105 |
106 | # Use Environment.ProcessId instead of Process.GetCurrentProcess().Id
107 | dotnet_diagnostic.CA1837.severity = error
108 |
109 | # Use Environment.ProcessPath instead of Process.GetCurrentProcess().MainModule.FileName
110 | dotnet_diagnostic.CA1839.severity = error
111 |
112 | # Use Environment.CurrentManagedThreadId instead of Thread.CurrentThread.ManagedThreadId
113 | dotnet_diagnostic.CA1840.severity = error
114 |
115 | # Prefer Dictionary Contains methods
116 | dotnet_diagnostic.CA1841.severity = error
117 |
118 | # Do not use WhenAll with a single task
119 | dotnet_diagnostic.CA1842.severity = error
120 |
121 | # Do not use WhenAll/WaitAll with a single task
122 | dotnet_diagnostic.CA1842.severity = error
123 | dotnet_diagnostic.CA1843.severity = error
124 |
125 | # Use span-based 'string.Concat'
126 | dotnet_diagnostic.CA1845.severity = error
127 |
128 | # Prefer AsSpan over Substring
129 | dotnet_diagnostic.CA1846.severity = error
130 |
131 | # Use string.Contains(char) instead of string.Contains(string) with single characters
132 | dotnet_diagnostic.CA1847.severity = error
133 |
134 | # Prefer static HashData method over ComputeHash
135 | dotnet_diagnostic.CA1850.severity = error
136 |
137 | # Possible multiple enumerations of IEnumerable collection
138 | dotnet_diagnostic.CA1851.severity = error
139 |
140 | # Unnecessary call to Dictionary.ContainsKey(key)
141 | dotnet_diagnostic.CA1853.severity = error
142 |
143 | # Prefer the IDictionary.TryGetValue(TKey, out TValue) method
144 | dotnet_diagnostic.CA1854.severity = error
145 |
146 | # Use Span.Clear() instead of Span.Fill()
147 | dotnet_diagnostic.CA1855.severity = error
148 |
149 | # Incorrect usage of ConstantExpected attribute
150 | dotnet_diagnostic.CA1856.severity = error
151 |
152 | # The parameter expects a constant for optimal performance
153 | dotnet_diagnostic.CA1857.severity = error
154 |
155 | # Use StartsWith instead of IndexOf
156 | dotnet_diagnostic.CA1858.severity = error
157 |
158 | # Avoid using Enumerable.Any() extension method
159 | dotnet_diagnostic.CA1860.severity = error
160 |
161 | # Avoid constant arrays as arguments
162 | dotnet_diagnostic.CA1861.severity = error
163 |
164 | # Use the StringComparison method overloads to perform case-insensitive string comparisons
165 | dotnet_diagnostic.CA1862.severity = error
166 |
167 | # Prefer the IDictionary.TryAdd(TKey, TValue) method
168 | dotnet_diagnostic.CA1864.severity = error
169 |
170 | # Use string.Method(char) instead of string.Method(string) for string with single char
171 | dotnet_diagnostic.CA1865.severity = error
172 | dotnet_diagnostic.CA1866.severity = error
173 | dotnet_diagnostic.CA1867.severity = error
174 |
175 | # Unnecessary call to 'Contains' for sets
176 | dotnet_diagnostic.CA1868.severity = error
177 |
178 | # Cache and reuse 'JsonSerializerOptions' instances
179 | dotnet_diagnostic.CA1869.severity = error
180 |
181 | # Use a cached 'SearchValues' instance
182 | dotnet_diagnostic.CA1870.severity = error
183 |
184 | # Microsoft .NET properties
185 | trim_trailing_whitespace = true
186 | csharp_preferred_modifier_order = public, private, protected, internal, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async:suggestion
187 | resharper_namespace_body = file_scoped
188 | dotnet_naming_rule.private_constants_rule.severity = warning
189 | dotnet_naming_rule.private_constants_rule.style = lower_camel_case_style
190 | dotnet_naming_rule.private_constants_rule.symbols = private_constants_symbols
191 | dotnet_naming_rule.private_instance_fields_rule.severity = warning
192 | dotnet_naming_rule.private_instance_fields_rule.style = lower_camel_case_style
193 | dotnet_naming_rule.private_instance_fields_rule.symbols = private_instance_fields_symbols
194 | dotnet_naming_rule.private_static_fields_rule.severity = warning
195 | dotnet_naming_rule.private_static_fields_rule.style = lower_camel_case_style
196 | dotnet_naming_rule.private_static_fields_rule.symbols = private_static_fields_symbols
197 | dotnet_naming_rule.private_static_readonly_rule.severity = warning
198 | dotnet_naming_rule.private_static_readonly_rule.style = lower_camel_case_style
199 | dotnet_naming_rule.private_static_readonly_rule.symbols = private_static_readonly_symbols
200 | dotnet_naming_style.lower_camel_case_style.capitalization = camel_case
201 | dotnet_naming_style.upper_camel_case_style.capitalization = pascal_case
202 | dotnet_naming_symbols.private_constants_symbols.applicable_accessibilities = private
203 | dotnet_naming_symbols.private_constants_symbols.applicable_kinds = field
204 | dotnet_naming_symbols.private_constants_symbols.required_modifiers = const
205 | dotnet_naming_symbols.private_instance_fields_symbols.applicable_accessibilities = private
206 | dotnet_naming_symbols.private_instance_fields_symbols.applicable_kinds = field
207 | dotnet_naming_symbols.private_static_fields_symbols.applicable_accessibilities = private
208 | dotnet_naming_symbols.private_static_fields_symbols.applicable_kinds = field
209 | dotnet_naming_symbols.private_static_fields_symbols.required_modifiers = static
210 | dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private
211 | dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field
212 | dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static, readonly
213 | dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none
214 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:none
215 | dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none
216 |
217 | # ReSharper properties
218 | resharper_object_creation_when_type_not_evident = target_typed
219 |
220 | # ReSharper inspection severities
221 | resharper_arrange_object_creation_when_type_evident_highlighting = error
222 | resharper_arrange_object_creation_when_type_not_evident_highlighting = error
223 | resharper_arrange_redundant_parentheses_highlighting = error
224 | resharper_arrange_static_member_qualifier_highlighting = error
225 | resharper_arrange_this_qualifier_highlighting = error
226 | resharper_arrange_type_member_modifiers_highlighting = none
227 | resharper_built_in_type_reference_style_for_member_access_highlighting = hint
228 | resharper_built_in_type_reference_style_highlighting = hint
229 | resharper_check_namespace_highlighting = none
230 | resharper_convert_to_using_declaration_highlighting = error
231 | resharper_css_not_resolved_highlighting = warning
232 | resharper_field_can_be_made_read_only_local_highlighting = none
233 | resharper_merge_into_logical_pattern_highlighting = warning
234 | resharper_merge_into_pattern_highlighting = error
235 | resharper_method_has_async_overload_highlighting = warning
236 | # because stop rider giving errors before source generators have run
237 | resharper_partial_type_with_single_part_highlighting = warning
238 | resharper_redundant_base_qualifier_highlighting = warning
239 | resharper_redundant_cast_highlighting = error
240 | resharper_redundant_empty_object_creation_argument_list_highlighting = error
241 | resharper_redundant_empty_object_or_collection_initializer_highlighting = error
242 | resharper_redundant_name_qualifier_highlighting = error
243 | resharper_redundant_suppress_nullable_warning_expression_highlighting = error
244 | resharper_redundant_using_directive_highlighting = error
245 | resharper_redundant_verbatim_string_prefix_highlighting = error
246 | resharper_redundant_lambda_signature_parentheses_highlighting = error
247 | resharper_replace_substring_with_range_indexer_highlighting = warning
248 | resharper_suggest_var_or_type_built_in_types_highlighting = error
249 | resharper_suggest_var_or_type_elsewhere_highlighting = error
250 | resharper_suggest_var_or_type_simple_types_highlighting = error
251 | resharper_unnecessary_whitespace_highlighting = error
252 | resharper_use_await_using_highlighting = warning
253 | resharper_use_deconstruction_highlighting = warning
254 |
255 | # Sort using and Import directives with System.* appearing first
256 | dotnet_sort_system_directives_first = true
257 |
258 | # Avoid "this." and "Me." if not necessary
259 | dotnet_style_qualification_for_field = false:error
260 | dotnet_style_qualification_for_property = false:error
261 | dotnet_style_qualification_for_method = false:error
262 | dotnet_style_qualification_for_event = false:error
263 |
264 | # Use language keywords instead of framework type names for type references
265 | dotnet_style_predefined_type_for_locals_parameters_members = true:error
266 | dotnet_style_predefined_type_for_member_access = true:error
267 |
268 | # Suggest more modern language features when available
269 | dotnet_style_object_initializer = true:error
270 | dotnet_style_collection_initializer = true:error
271 | dotnet_style_coalesce_expression = false:error
272 | dotnet_style_null_propagation = true:error
273 | dotnet_style_explicit_tuple_names = true:error
274 |
275 | # Prefer "var" everywhere
276 | csharp_style_var_for_built_in_types = true:error
277 | csharp_style_var_when_type_is_apparent = true:error
278 | csharp_style_var_elsewhere = true:error
279 |
280 | # Prefer method-like constructs to have a block body
281 | csharp_style_expression_bodied_methods = true:error
282 | csharp_style_expression_bodied_local_functions = true:error
283 | csharp_style_expression_bodied_constructors = true:error
284 | csharp_style_expression_bodied_operators = true:error
285 | resharper_place_expr_method_on_single_line = false
286 |
287 | # Prefer property-like constructs to have an expression-body
288 | csharp_style_expression_bodied_properties = true:error
289 | csharp_style_expression_bodied_indexers = true:error
290 | csharp_style_expression_bodied_accessors = true:error
291 |
292 | # Suggest more modern language features when available
293 | csharp_style_pattern_matching_over_is_with_cast_check = true:error
294 | csharp_style_pattern_matching_over_as_with_null_check = true:error
295 | csharp_style_inlined_variable_declaration = true:suggestion
296 | csharp_style_throw_expression = true:suggestion
297 | csharp_style_conditional_delegate_call = true:suggestion
298 |
299 | # Newline settings
300 | #csharp_new_line_before_open_brace = all:error
301 | resharper_max_array_initializer_elements_on_line = 1
302 | csharp_new_line_before_else = true
303 | csharp_new_line_before_catch = true
304 | csharp_new_line_before_finally = true
305 | csharp_new_line_before_members_in_object_initializers = true
306 | csharp_new_line_before_members_in_anonymous_types = true
307 | resharper_wrap_before_first_type_parameter_constraint = true
308 | resharper_wrap_extends_list_style = chop_always
309 | resharper_wrap_after_dot_in_method_calls = false
310 | resharper_wrap_before_binary_pattern_op = false
311 | resharper_wrap_object_and_collection_initializer_style = chop_always
312 | resharper_place_simple_initializer_on_single_line = false
313 |
314 | # space
315 | resharper_space_around_lambda_arrow = true
316 |
317 | dotnet_style_require_accessibility_modifiers = never:error
318 | resharper_place_type_constraints_on_same_line = false
319 | resharper_blank_lines_inside_namespace = 0
320 | resharper_blank_lines_after_file_scoped_namespace_directive = 1
321 | resharper_blank_lines_inside_type = 0
322 |
323 | insert_final_newline = false
324 | resharper_place_attribute_on_same_line = false
325 | resharper_space_around_lambda_arrow = true
326 | resharper_place_constructor_initializer_on_same_line = false
327 |
328 | #braces https://www.jetbrains.com/help/resharper/EditorConfig_CSHARP_CSharpCodeStylePageImplSchema.html#Braces
329 | resharper_braces_for_ifelse = required
330 | resharper_braces_for_foreach = required
331 | resharper_braces_for_while = required
332 | resharper_braces_for_dowhile = required
333 | resharper_braces_for_lock = required
334 | resharper_braces_for_fixed = required
335 | resharper_braces_for_for = required
336 |
337 | resharper_return_value_of_pure_method_is_not_used_highlighting = error
338 |
339 | resharper_all_underscore_local_parameter_name_highlighting = none
340 |
341 | resharper_misleading_body_like_statement_highlighting = error
342 |
343 | resharper_redundant_record_class_keyword_highlighting = error
344 |
345 | resharper_redundant_extends_list_entry_highlighting = error
346 |
347 | # Xml files
348 | [*.{xml,config,nuspec,resx,vsixmanifest,csproj,targets,props,fsproj}]
349 | indent_size = 2
350 | # https://www.jetbrains.com/help/resharper/EditorConfig_XML_XmlCodeStylePageSchema.html#resharper_xml_blank_line_after_pi
351 | resharper_blank_line_after_pi = false
352 | resharper_space_before_self_closing = true
353 | ij_xml_space_inside_empty_tag = true
354 |
355 | [*.json]
356 | indent_size = 2
357 |
358 | # Verify settings
359 | [*.{received,verified}.{txt,xml,json,md,sql,csv,html,htm,md}]
360 | charset = utf-8-bom
361 | end_of_line = lf
362 | indent_size = unset
363 | indent_style = unset
364 | insert_final_newline = false
365 | tab_width = unset
366 | trim_trailing_whitespace = false
--------------------------------------------------------------------------------
/src/.gitattributes:
--------------------------------------------------------------------------------
1 | * text
2 | *.snk binary
3 | *.png binary
4 |
5 | *.verified.txt text eol=lf working-tree-encoding=UTF-8
6 | *.verified.xml text eol=lf working-tree-encoding=UTF-8
7 | *.verified.json text eol=lf working-tree-encoding=UTF-8
8 |
9 | .editorconfig text eol=lf working-tree-encoding=UTF-8
10 | Shared.sln.DotSettings text eol=lf working-tree-encoding=UTF-8
--------------------------------------------------------------------------------
/src/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | 10.0.0
4 | 1.0.0
5 | preview
6 | GraphQL, Validation, FluentValidation
7 | Add FluentValidation (https://fluentvalidation.net/) support to GraphQL.net (https://github.com/graphql-dotnet/graphql-dotnet)
8 | CS1591;NU1608;NU1109
9 | true
10 | true
11 | true
12 | true
13 |
14 |
--------------------------------------------------------------------------------
/src/Directory.Packages.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | true
4 | true
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/GraphQL.FluentValidation/ArgumentTypeCacheBag.cs:
--------------------------------------------------------------------------------
1 | static class ArgumentTypeCacheBag
2 | {
3 | const string key = "GraphQL.FluentValidation.ValidatorTypeCache";
4 |
5 | public static void SetCache(this ExecutionOptions options, IValidatorCache cache)
6 | {
7 | // ReSharper disable once ConditionIsAlwaysTrueOrFalse
8 | // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
9 | if (options.UserContext == null)
10 | {
11 | options.UserContext = new Dictionary
12 | {
13 | { key, cache }
14 | };
15 | return;
16 | }
17 |
18 | AddValidatorCache(options.UserContext, cache);
19 | }
20 |
21 | internal static void AddValidatorCache(this IDictionary dictionary, IValidatorCache cache) =>
22 | dictionary.Add(key, cache);
23 |
24 | public static IValidatorCache GetCache(this IResolveFieldContext context)
25 | {
26 | var userContext = context.UserContext;
27 | if (userContext == null)
28 | {
29 | throw new("Expected UserContext to be of type IDictionary.");
30 | }
31 |
32 | if (!userContext.TryGetValue(key, out var result))
33 | {
34 | throw new($"Could not extract {nameof(IValidatorCache)} from {nameof(IResolveFieldContext)}.{nameof(IResolveFieldContext.UserContext)}. It is possible {nameof(FluentValidationExtensions)}.{nameof(FluentValidationExtensions.UseFluentValidation)} was not used.");
35 | }
36 |
37 | return (IValidatorCache)result!;
38 | }
39 | }
--------------------------------------------------------------------------------
/src/GraphQL.FluentValidation/ArgumentValidation.cs:
--------------------------------------------------------------------------------
1 | using FluentValidation.Results;
2 |
3 | namespace GraphQL.FluentValidation;
4 |
5 | ///
6 | /// Low level validation API for extensibility scenarios.
7 | ///
8 | public static class ArgumentValidation
9 | {
10 | ///
11 | /// Validate an instance
12 | ///
13 | public static Task ValidateAsync(IValidatorCache cache, Type type, TArgument instance, IDictionary userContext)
14 | => ValidateAsync(cache, type, instance, userContext, null);
15 |
16 | ///
17 | /// Validate an instance
18 | ///
19 | public static async Task ValidateAsync(IValidatorCache cache, Type type, TArgument instance, IDictionary userContext, IServiceProvider? provider, Cancel cancel = default)
20 | {
21 | var currentType = (Type?)type;
22 | var validationContext = default(ValidationContext);
23 |
24 | while (currentType != null)
25 | {
26 | if (cache.TryGetValidators(currentType, provider, out var buildAll))
27 | {
28 | validationContext ??= BuildValidationContext(instance, userContext);
29 |
30 | var tasks = buildAll.Select(_ => _.ValidateAsync(validationContext, cancel));
31 | var validationResults = await Task.WhenAll(tasks);
32 |
33 | var results = validationResults
34 | .SelectMany(result => result.Errors);
35 |
36 | ThrowIfResults(results);
37 | }
38 |
39 | currentType = currentType.BaseType;
40 | }
41 | }
42 |
43 | ///
44 | /// Validate an instance
45 | ///
46 | public static void Validate(IValidatorCache cache, Type type, TArgument instance, IDictionary userContext)
47 | => Validate(cache, type, instance, userContext, null);
48 |
49 | ///
50 | /// Validate an instance
51 | ///
52 | public static void Validate(IValidatorCache cache, Type type, TArgument instance, IDictionary userContext, IServiceProvider? provider)
53 | {
54 | if (instance == null)
55 | {
56 | return;
57 | }
58 |
59 | var currentType = (Type?)type;
60 | var validationContext = default(ValidationContext);
61 |
62 | while (currentType != null)
63 | {
64 | if (cache.TryGetValidators(currentType, provider, out var buildAll))
65 | {
66 | validationContext ??= BuildValidationContext(instance, userContext);
67 | var results = buildAll
68 | .SelectMany(validator => validator.Validate(validationContext).Errors);
69 |
70 | ThrowIfResults(results);
71 | }
72 |
73 | currentType = currentType.BaseType;
74 | }
75 | }
76 |
77 | static void ThrowIfResults(IEnumerable results)
78 | {
79 | var list = results.ToList();
80 | if (list.Count != 0)
81 | {
82 | throw new ValidationException(list);
83 | }
84 | }
85 |
86 | static ValidationContext BuildValidationContext(TArgument instance, IDictionary userContext)
87 | {
88 | ValidationContext validationContext = new(instance);
89 | validationContext.RootContextData.Add("UserContext", userContext);
90 | return validationContext;
91 | }
92 | }
--------------------------------------------------------------------------------
/src/GraphQL.FluentValidation/FluentValidationExtensions.cs:
--------------------------------------------------------------------------------
1 | using GraphQL.Instrumentation;
2 | using GraphQL.Types;
3 |
4 | namespace GraphQL;
5 |
6 | ///
7 | /// Extensions to GraphQL to enable FluentValidation.
8 | ///
9 | public static partial class FluentValidationExtensions
10 | {
11 | ///
12 | /// Adds a FieldMiddleware to the GraphQL pipeline that converts a to s./>
13 | ///
14 | public static ExecutionOptions UseFluentValidation(this ExecutionOptions executionOptions, IValidatorCache validatorTypeCache)
15 | {
16 | validatorTypeCache.Freeze();
17 | executionOptions.SetCache(validatorTypeCache);
18 | return executionOptions;
19 | }
20 |
21 | ///
22 | /// Adds a FieldMiddleware to the GraphQL pipeline that converts a to s./>
23 | ///
24 | public static void UseFluentValidation(this ISchema schema) =>
25 | schema.FieldMiddleware.Use(new ValidationMiddleware());
26 | }
--------------------------------------------------------------------------------
/src/GraphQL.FluentValidation/FluentValidationExtensions_GetArgument.cs:
--------------------------------------------------------------------------------
1 | namespace GraphQL;
2 |
3 | public static partial class FluentValidationExtensions
4 | {
5 | ///
6 | /// Wraps to validate the resulting argument instance.
7 | /// Uses to perform validation.
8 | /// If a occurs it will be converted to s by a field middleware.
9 | ///
10 | public static TArgument GetValidatedArgument(this IResolveFieldContext context, string name) =>
11 | GetValidatedArgument(context, name, default!);
12 |
13 | ///
14 | /// Wraps to validate the resulting argument instance.
15 | /// Uses to perform validation.
16 | /// If a occurs it will be converted to s by a field middleware.
17 | ///
18 | public static TArgument GetValidatedArgument(this IResolveFieldContext context, string name, TArgument defaultValue)
19 | {
20 | var argument = context.GetArgument(name, defaultValue);
21 | var validatorCache = context.GetCache();
22 | ArgumentValidation.Validate(validatorCache, typeof(TArgument), argument, context.UserContext, context.RequestServices);
23 | return argument!;
24 | }
25 |
26 | ///
27 | /// Wraps to validate the resulting argument instance.
28 | /// Uses to perform validation.
29 | /// If a occurs it will be converted to s by a field middleware.
30 | ///
31 | public static object GetValidatedArgument(this IResolveFieldContext context, Type argumentType, string name) =>
32 | GetValidatedArgument(context, argumentType, name, null!);
33 |
34 | ///
35 | /// Wraps to validate the resulting argument instance.
36 | /// Uses to perform validation.
37 | /// If a occurs it will be converted to s by a field middleware.
38 | ///
39 | public static object GetValidatedArgument(this IResolveFieldContext context, Type argumentType, string name, object defaultValue)
40 | {
41 | var argument = context.GetArgument(argumentType, name, defaultValue);
42 | var validatorCache = context.GetCache();
43 | ArgumentValidation.Validate(validatorCache, argumentType, argument, context.UserContext, context.RequestServices);
44 | return argument!;
45 | }
46 | }
--------------------------------------------------------------------------------
/src/GraphQL.FluentValidation/FluentValidationExtensions_GetArgumentAsync.cs:
--------------------------------------------------------------------------------
1 | namespace GraphQL;
2 |
3 | public static partial class FluentValidationExtensions
4 | {
5 | ///
6 | /// Wraps to validate the resulting argument instance.
7 | /// Uses to perform validation.
8 | /// If a occurs it will be converted to s by a field middleware.
9 | ///
10 | public static Task GetValidatedArgumentAsync(this IResolveFieldContext context, string name) =>
11 | GetValidatedArgumentAsync(context, name, default!);
12 |
13 | ///
14 | /// Wraps to validate the resulting argument instance.
15 | /// Uses to perform validation.
16 | /// If a occurs it will be converted to s by a field middleware.
17 | ///
18 | public static async Task GetValidatedArgumentAsync(this IResolveFieldContext context, string name, TArgument defaultValue)
19 | {
20 | var argument = context.GetArgument(name, defaultValue);
21 | var validatorCache = context.GetCache();
22 | await ArgumentValidation.ValidateAsync(validatorCache, typeof(TArgument), argument, context.UserContext, context.RequestServices);
23 | return argument!;
24 | }
25 |
26 | ///
27 | /// Wraps to validate the resulting argument instance.
28 | /// Uses to perform validation.
29 | /// If a occurs it will be converted to s by a field middleware.
30 | ///
31 | public static Task