Skip to content

Commit 59fd834

Browse files
committed
WIP. upgrade docusaurus, added input unions section
1 parent 2a100f1 commit 59fd834

File tree

9 files changed

+5643
-4435
lines changed

9 files changed

+5643
-4435
lines changed

docs/advanced/custom-scalars.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
id: custom-scalars
33
title: Custom Scalars
44
sidebar_label: Custom Scalars
5-
sidebar_position: 3
5+
sidebar_position: 4
66
---
77

88
Scalars are the most basic, fundamental unit of content in GraphQL. It is one of two leaf types (the other being [enums](../types/enums)).

docs/advanced/directives.md

Lines changed: 40 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ public class MyInvalidDirective : GraphDirective
111111

112112
Execution Directives are applied to query documents and executed only on the request in which they are encountered.
113113

114-
### Example: @include
114+
### Custom Example: @include
115115

116116
This is the code for the built in `@include` directive:
117117

@@ -228,9 +228,45 @@ Batch extensions work differently than standard field resolvers; they don't reso
228228

229229
Type System directives are applied to schema items and executed at start up while the schema is being created.
230230

231-
### Example: @toLower
231+
### @oneOf
232232

233-
This directive will extend the resolver of a field, as its declared **in the schema**, to turn any strings into lower case letters.
233+
*See [input unions](input-unions.md) for further details*
234+
235+
### @deprecated
236+
237+
The `@deprecated` directive is a built in type system directive provided by graphql to indicate deprecation on a field definition or enum value. Below is the code for its implementation.
238+
239+
```csharp
240+
public sealed class DeprecatedDirective : GraphDirective
241+
{
242+
[DirectiveLocations(DirectiveLocation.FIELD_DEFINITION | DirectiveLocation.ENUM_VALUE)]
243+
public IGraphActionResult Execute([FromGraphQL("reason")] string reason = "No longer supported")
244+
{
245+
if (this.DirectiveTarget is IGraphField field)
246+
{
247+
field.IsDeprecated = true;
248+
field.DeprecationReason = reason;
249+
}
250+
else if (this.DirectiveTarget is IEnumValue enumValue)
251+
{
252+
enumValue.IsDeprecated = true;
253+
enumValue.DeprecationReason = reason;
254+
}
255+
256+
return this.Ok();
257+
}
258+
}
259+
```
260+
261+
This Directive:
262+
263+
- Targets a FIELD_DEFINITION or ENUM_VALUE.
264+
- Marks the field or enum value as deprecated and attaches the provided deprecation reason
265+
- The directive is executed once per field definition and enum value its applied to when the schema is created.
266+
267+
### Custom Example: @toLower
268+
269+
This custom example directive will extend the resolver of a field, as its declared **in the schema**, to turn any strings into lower case letters.
234270

235271
```csharp title="Example: ToLowerDirective.cs"
236272
public class ToLowerDirective : GraphDirective
@@ -275,38 +311,6 @@ This Directive:
275311
Notice the difference in this type system directive vs. the `@toUpper` execution directive above. Where as toUpper was declared as a PostResolver on the document part, this directive extends the primary resolver of an `IGraphField` and affects ALL queries that request this field.
276312
:::
277313

278-
### Example: @deprecated
279-
280-
The `@deprecated` directive is a built in type system directive provided by graphql to indicate deprecation on a field definition or enum value. Below is the code for its implementation.
281-
282-
```csharp
283-
public sealed class DeprecatedDirective : GraphDirective
284-
{
285-
[DirectiveLocations(DirectiveLocation.FIELD_DEFINITION | DirectiveLocation.ENUM_VALUE)]
286-
public IGraphActionResult Execute([FromGraphQL("reason")] string reason = "No longer supported")
287-
{
288-
if (this.DirectiveTarget is IGraphField field)
289-
{
290-
field.IsDeprecated = true;
291-
field.DeprecationReason = reason;
292-
}
293-
else if (this.DirectiveTarget is IEnumValue enumValue)
294-
{
295-
enumValue.IsDeprecated = true;
296-
enumValue.DeprecationReason = reason;
297-
}
298-
299-
return this.Ok();
300-
}
301-
}
302-
```
303-
304-
This Directive:
305-
306-
- Targets a FIELD_DEFINITION or ENUM_VALUE.
307-
- Marks the field or enum value as deprecated and attaches the provided deprecation reason
308-
- The directive is executed once per field definition and enum value its applied to when the schema is created.
309-
310314
### Applying Type System Directives
311315

312316
#### Using the `[ApplyDirective]` attribute
@@ -507,4 +511,4 @@ public sealed class UnRedactDirective : GraphDirective
507511
## Demo Project
508512

509513
See the [Demo Projects](../reference/demo-projects.md) page for a demonstration on creating a type system directive for extending a field resolver and an execution directives
510-
that manipulates a string field result at runtime.
514+
that manipulates a string field result at runtime.

docs/advanced/graph-action-results.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
id: graph-action-results
33
title: Action Results
44
sidebar_label: Action Results
5-
sidebar_position: 4
5+
sidebar_position: 5
66
---
77

88
## What is an Action Result?

docs/advanced/input-unions.md

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
---
2+
id: input-unions
3+
title: Input Unions
4+
sidebar_label: Input Unions
5+
sidebar_position: 3
6+
---
7+
8+
Input unions (Spec § [3.10.1](https://spec.graphql.org/September2025/#sec-OneOf-Input-Objects)) are a variant on standard input objects such that only one of the defined fields may be used in a query. This can be helpful if you have an input object, such as search parameters, that gives multiple ways to search, but you want the user submitting a query to choose exactly one option to search by.
9+
10+
By definition an input union is a standard [input object](../types/input-objects.md) (a class or a struct), all rules of standard input objects apply (i.e. field names must be unique, no use of interfaces etc.). However...
11+
12+
**Input unions:**<br/>
13+
✅ MUST have no default values declared on any field.<br/>
14+
✅ MUST have all nullable fields
15+
16+
:::warning
17+
A declaration exception will be thrown and the server will fail to start if either of these rules are violated for any declared input union.
18+
:::
19+
20+
21+
## Creating An Input Union
22+
An object can be declared as an input union in multiple ways:
23+
24+
### Using Attribution
25+
26+
Use the `[OneOf]` attribute to mark an object as ALWAYS being an input union. Any place the class or struct is referenced as an input to a method it will be handled as an input union.
27+
28+
```csharp title="Declaring an input union with the [OneOf] attribute"
29+
// highlight-next-line
30+
[OneOf]
31+
[GraphType(InputName = "SearchOptions")]
32+
public class SearchDonutParams
33+
{
34+
public string Name {get; set;}
35+
public Flavor? Flavor {get; set; } // assume flavor is an enum
36+
}
37+
```
38+
39+
```csharp title="Using an Input Union in a Controller"
40+
public class BakeryController : GraphController
41+
{
42+
[QueryRoot("findDonuts")]
43+
// highlight-next-line
44+
public List<Donut> FindDonuts(SearchDonutParams search)
45+
{
46+
/// guaranteed that only one value (name or flavor) is non-null
47+
return [];
48+
}
49+
}
50+
```
51+
52+
```graphql title="Equivilant schema type definition"
53+
## relevant graphql type generated
54+
input SearchOptions @oneOf {
55+
name: String
56+
flavor: Flavor
57+
}
58+
```
59+
60+
### Inherit from GraphInputUnion
61+
62+
Creating a class that inherits from `GraphInputUnion` works in the same way as using `[OneOf]` but adds some additional quality of life features in terms of metadata and default value handling.
63+
64+
_See below for details on using `GraphInputUnion`_
65+
66+
67+
```csharp title="Inheriting from GraphInputUnion"
68+
[GraphType(InputName "SearchParams")]
69+
// highlight-next-line
70+
public class SearchDonutParams : GraphInputUnion
71+
{
72+
public string Name {get; set;}
73+
public Flavor? Flavor {get; set; } // assume flavor is an enum
74+
}
75+
```
76+
77+
```csharp title="Using an Input Union in a Controller"
78+
public class BakeryController : GraphController
79+
{
80+
[QueryRoot("findDonuts")]
81+
// highlight-next-line
82+
public List<Donut> FindDonuts(SearchDonutParams search)
83+
{
84+
/// guaranteed that only one value (name or flavor) is non-null
85+
return [];
86+
}
87+
}
88+
```
89+
90+
```graphql title="Equivilant schema type definition"
91+
## relevant graphql type generated
92+
input SearchOptions @oneOf {
93+
name: String
94+
flavor: Flavor
95+
}
96+
```
97+
98+
## Nullable Fields
99+
The specification defines an input union as *"a special variant of Input Object where exactly one field must be set and non-null, all others being omitted."* (Spec § [3.10.1](https://spec.graphql.org/September2025/#sec-OneOf-Input-Objects)). As such, all properties declared on a class or struct that is being used as an input union must be nullable, the supplied query MUST set exactly one field to a non-null value on a query document.
100+
101+
```csharp title="Example Scenarios"
102+
103+
// 🧨 FAIL: Flavor is non-nullable. A graph declaration exception will be thrown at start up.
104+
[OneOf]
105+
public class SearchDonutParams
106+
{
107+
public string Name {get; set;}
108+
public Flavor Flavor {get; set; } // assume flavor is an enum
109+
}
110+
111+
// 🧨 FAIL: Name declares a default value. A graph declaration exception will be thrown at start up.
112+
[OneOf]
113+
public class SearchDonutParams
114+
{
115+
public SearchDonutParams
116+
{
117+
this.Name = "%";
118+
}
119+
120+
public string Name {get; set;}
121+
public Flavor? Flavor {get; set; } // assume flavor is an enum
122+
}
123+
124+
// ✅ SUCCESS
125+
[OneOf]
126+
public class SearchDonutParams
127+
{
128+
public string Name {get; set;}
129+
public Flavor? Flavor {get; set; }
130+
}
131+
```
132+
133+
## Using `GraphInputUnion`
134+
This special base type can be used to expose some additional, quality of life methods for dealing with nullability and default values.
135+
136+
```csharp
137+
public abstract class GraphInputUnion
138+
{
139+
// Will return the value, if it was supplied on the query, otherwise fallbackValue.
140+
// this method is is heavily optimized to be performant at runtime
141+
public TReturn ValueOrDefault<TValue, TReturn>(Expression<Func<TObject, TValue>> selector, TReturn fallbackValue = default);
142+
}
143+
144+
[GraphType(InputName = "SearchParams")]
145+
public class SearchDonutParams : GraphInputUnion
146+
{
147+
public string Name {get; set;}
148+
149+
public Flavor? Flavor {get; set; } // assume flavor is an enum
150+
}
151+
152+
153+
// Sample Usage
154+
public class BakeryController : GraphController
155+
{
156+
[QueryRoot("findDonuts")]
157+
public List<Donut> FindDonuts(SearchDonutParams search)
158+
{
159+
InternalSearchParams internalParams = new();
160+
internalParams.Name = search.ValueOrDefault(x => x.Name, "%");
161+
internalParams.Flavor = search.ValueOrDefault(x => x.Flavor, Flavor.All);
162+
return _service.SearchDonuts(internalParams);
163+
}
164+
}
165+
```
166+
167+
:::info
168+
The `ValueOrDefault()` method will return a type of the fallback value, NOT of the input object property. This allows you to return non-null defaults in place of nullable values that must be passed on the input object. This should greatly reduce bloat in transferring query supplied values and reasonable fallbacks when necessary. When returning non-reference types, they must have compatibility between the nullable and non-nullable versions (e.g. `int` and `int?`)
169+
:::

docs/advanced/multiple-schema.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
id: multi-schema-support
33
title: Multi-Schema Support
44
sidebar_label: Multi-Schema Support
5-
sidebar_position: 5
5+
sidebar_position: 6
66
---
77

88
GraphQL ASP.NET supports multiple schemas on the same server out of the box. Each schema is recognized by its concrete .NET type.

docs/types/input-objects.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,4 +297,7 @@ public class Donut
297297
298298
:::caution
299299
Enum values used for the default value of input object properties MUST also exist as values in the schema or an exception will be thrown.
300-
:::
300+
:::
301+
302+
## Input Unions / @oneOf
303+
See [Input Unions](../advanced/input-unions.md) in the advanced section for details on using the `@oneOf` directive.

docusaurus.config.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
// @ts-check
22
// Note: type annotations allow type checking and IDEs autocompletion
33

4-
const lightCodeTheme = require('prism-react-renderer/themes/github');
5-
const darkCodeTheme = require('prism-react-renderer/themes/palenight');
4+
const {themes} = require('prism-react-renderer');
5+
const lightCodeTheme = themes.github;
6+
const darkCodeTheme = themes.vsDark;
67

78
/** @type {import('@docusaurus/types').Config} */
89
const config = {
@@ -11,9 +12,14 @@ const config = {
1112
url: 'https://graphql-aspnet.github.io',
1213
baseUrl: '/',
1314
onBrokenLinks: 'throw',
14-
onBrokenMarkdownLinks: 'warn',
1515
favicon: 'img/favicon.ico',
1616

17+
markdown: {
18+
hooks: {
19+
onBrokenMarkdownLinks: 'warn',
20+
},
21+
},
22+
1723

1824
// GitHub pages deployment config.
1925
// If you aren't using GitHub pages, you don't need these.

package.json

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,17 @@
1414
"write-heading-ids": "docusaurus write-heading-ids"
1515
},
1616
"dependencies": {
17-
"@docusaurus/core": "^2.4.0",
18-
"@docusaurus/preset-classic": "^2.4.0",
19-
"@mdx-js/react": "^1.6.22",
20-
"clsx": "^1.2.1",
21-
"prism-react-renderer": "^1.3.5",
22-
"react": "^17.0.2",
23-
"react-dom": "^17.0.2"
17+
"@docusaurus/core": "^3.9.0",
18+
"@docusaurus/preset-classic": "^3.9.0",
19+
"@mdx-js/react": "^3.1.0",
20+
"clsx": "^2.1.1",
21+
"prism-react-renderer": "^2.4.1",
22+
"react": "^18.3.1",
23+
"react-dom": "^18.3.1"
2424
},
2525
"devDependencies": {
26-
"@docusaurus/module-type-aliases": "^2.4.0"
26+
"@docusaurus/module-type-aliases": "^3.9.0",
27+
"@docusaurus/types": "^3.9.0"
2728
},
2829
"browserslist": {
2930
"production": [
@@ -38,6 +39,6 @@
3839
]
3940
},
4041
"engines": {
41-
"node": ">=16.14"
42+
"node": ">=18.0"
4243
}
4344
}

0 commit comments

Comments
 (0)