Skip to content

Commit 3b46003

Browse files
v1.0.0-RC3 Updates (#37)
* Incremented version * typos and clarifications
1 parent 4c20834 commit 3b46003

File tree

12 files changed

+178
-149
lines changed

12 files changed

+178
-149
lines changed

docs/advanced/custom-scalars.md

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Lets say we wanted to build a scalar called `Money` that can handle both an amou
1717
public class InventoryController : GraphController
1818
{
1919
[QueryRoot("search")]
20+
// highlight-next-line
2021
public IEnumerable<Product> Search(Money minPrice)
2122
{
2223
return _service.RetrieveProducts(
@@ -41,6 +42,7 @@ public class Money
4142

4243
```graphql title="Using the Money Scalar"
4344
query {
45+
# highlight-next-line
4446
search(minPrice: "$18.45"){
4547
id
4648
name
@@ -99,42 +101,44 @@ public interface ILeafValueResolver
99101
- This method is used when generated default values for field arguments and input object fields via introspection queries.
100102
- This method must return a value exactly as it would appear in a schema type definition For example, strings must be surrounded by quotes.
101103

102-
- `ValidateObject(object)`: A method used when validating data returned from a a field resolver. GraphQL will call this method and provide an object instance to determine if its acceptable and can be used in a query.
104+
- `ValidateObject(object)`: A method used when validating data returned from a a field resolver. GraphQL will call this method and provide an object instance to determine if its acceptable and can be used in a query result.
103105

104106
:::note
105-
`ValidateObject(object)` should not attempt to enforce nullability rules. In general, all scalars should return `true` for a validation result if the provided object is `null`.
107+
`ValidateObject(object)` should not attempt to enforce nullability rules. In general, all scalars "could be null" depending on their usage in a schema. All scalars should return `true` for a validation result if the provided object is `null`.
106108
:::
107109

108-
### ILeafValueResolver Members
110+
### ILeafValueResolver
109111

110-
- `Resolve(ReadOnlySpan<char>)`: A resolver function capable of converting an array of characters into the internal representation of the scalar.
112+
ILeafValueResolver contains a single method:
113+
114+
- `Resolve(ReadOnlySpan<char>)`: A resolver function used for converting an array of characters into the internal representation of the scalar.
111115

112116
#### Dealing with Escaped Strings
113117

114118
The span provided to `ILeafValueResolver.Resolve` will be the raw data read from the query document. If the data represents a string, it will be provided in its delimited format. This means being surrounded by quotes as well as containing escaped characters (including escaped unicode characters):
115119

116-
Example string data:
120+
Example data:
117121

118122
- `"quoted string"`
119123
- `"""triple quoted string"""`
120124
- `"With \"\u03A3scaped ch\u03B1racters\""`;
121125

122-
The static type `GraphQLStrings` provides a handy static method for unescaping the data if you don't need to do anything special with it, `GraphQLStrings.UnescapeAndTrimDelimiters`.
126+
The static method `GraphQLStrings.UnescapeAndTrimDelimiters` provides a handy way for unescaping the data if you don't need to do anything special with it.
123127

124-
Calling `UnescapeAndTrimDelimiters` with the previous examples produces:
128+
Calling `GraphQLStrings.UnescapeAndTrimDelimiters` with the previous examples produces:
125129

126130
- `quoted string`
127131
- `triple quoted string`
128132
- `With "Σscaped chαracters"`
129133

130134
#### Indicating an Error
131135

132-
When resolving input values with `Resolve()`, if the provided value is not usable and must be rejected then the entire query document must be rejected. For instance, if a document contained the value `"$15.R0"` for our money scalar it would need to be rejected because `15.R0` cannot be converted to a decimal decimal.
136+
When resolving incoming values with `Resolve()`, if the provided value is not usable and must be rejected then the entire query document must be rejected. For instance, if a document contained the value `"$15.R0"` for our money scalar it would need to be rejected because `15.R0` cannot be converted to a decimal.
133137

134-
Throw an exception when this happens and GraphQL will automatically generate an appropriate response with the correct origin information indicating the line and column in the query document where the error occurred. However, like with any other encounterd exception, GraphQL will obfuscate it to a generic message and only expose your exception details if allowed by the [schema configuration](../reference/schema-configuration).
138+
Throw an exception when this happens and GraphQL will automatically generate an appropriate response with the correct origin information indicating the line and column in the query document where the error occurred. However, like with any other encounterd exception, the library will obfuscate it to a generic message and only expose your exception details if allowed by the [schema configuration](../reference/schema-configuration).
135139

136140
:::tip Pro Tip!
137-
If you throw `UnresolvedValueException` your error message will be delivered verbatim to the requestor as part of the response message instead of being obfuscated.
141+
If you throw the special `UnresolvedValueException` your error message will be delivered verbatim to the requestor as part of the response message instead of being obfuscated.
138142
:::
139143

140144
### Example: Money Scalar
@@ -219,7 +223,7 @@ services.AddGraphQL();
219223
```
220224

221225
:::info
222-
Since our scalar is represented by a .NET class, if we don't pre-register it GraphQL will attempt to parse the `Money` class as an object graph type. Once registered as a scalar, any attempt to use `Money` as an object graph type will cause an exception.
226+
Since our scalar is represented by a .NET class, if we don't pre-register it GraphQL will attempt to parse the `Money` class as an input object graph type. Once registered as a scalar, any attempt to use `Money` as an object graph type will cause an exception.
223227
:::
224228

225229
## @specifiedBy Directive
@@ -274,7 +278,7 @@ A few points about designing your scalar:
274278
- Scalar types should be simple and work in isolation.
275279
- The `ReadOnlySpan<char>` provided to `ILeafValueResolver.Resolve` should be all the data needed to generate a value, there should be no need to perform side effects or fetch additional data.
276280
- Scalar types should not track any state, depend on any stateful objects, or attempt to use any sort of dependency injection.
277-
- `ILeafValueResolver.Resolve` must be **FAST**! Since your resolver is used to construct an initial query plan from a text document, it'll be called many orders of magnitude more often than any other method.
281+
- `ILeafValueResolver.Resolve` must be **FAST**! Since your resolver is used to construct an initial query plan from the raw query text, it'll be called many orders of magnitude more often than any other method.
278282

279283
### Aim for Fewer Scalars
280284

@@ -295,24 +299,22 @@ public class InventoryController : GraphController
295299

296300
public class Money
297301
{
298-
public string Symbol { get; }
299-
public decimal Price { get; }
302+
public string Symbol { get; set; }
303+
public decimal Price { get; set; }
300304
}
301305
```
302306

303307
```graphql title="Using the Money Input Object"
304308
query {
305-
search(minPrice: {
306-
symbol: "$"
307-
price: 18.45}){
309+
search(minPrice: {symbol: "$" price: 18.45}){
308310
id
309311
name
310312
}
311313
}
312314
```
313315

314316

315-
This is a lot more flexible. We can add more properties to `Money` when needed and not break existing queries. Whereas with a scalar if we change the acceptable format of the string data any existing query text will now be invalid. It is almost always better to represent your data as an object or input object rather than a scalar.
317+
This is a lot more flexible. We can add more properties to `Money` when needed and not break existing queries. Whereas with a scalar if we change the acceptable format of the string data any existing applications using our graph may need to be updated. It is almost always better to represent your data as an input object rather than a custom scalar.
316318

317319
:::caution Be Careful
318320
Creating a custom scalar should be a last resort, not a first option.

docs/advanced/directives.md

Lines changed: 73 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -79,26 +79,26 @@ The following properties are available to all directive action methods:
7979
Directives may contain input arguments just like fields. However, its important to note that while a directive may declare multiple action methods for different locations to seperate your logic better, it is only a single entity in the schema. ALL action methods must share a common signature. The runtime will throw an exception while creating your schema if the signatures of the action methods differ.
8080

8181
```csharp title="Arguments for Directives"
82-
public class MyValidDirective : GraphDirective
83-
{
84-
[DirectiveLocations(DirectiveLocation.FIELD)]
85-
public IGraphActionResult ExecuteField(int arg1, string arg2) { /.../ }
82+
public class MyValidDirective : GraphDirective
83+
{
84+
[DirectiveLocations(DirectiveLocation.FIELD)]
85+
public IGraphActionResult ExecuteField(int arg1, string arg2) { /.../ }
8686

87-
[DirectiveLocations(DirectiveLocation.FRAGMENT_SPREAD)]
88-
public Task<IGraphActionResult> ExecuteFragSpread(int arg1, string arg2) { /.../ }
89-
}
87+
[DirectiveLocations(DirectiveLocation.FRAGMENT_SPREAD)]
88+
public Task<IGraphActionResult> ExecuteFragSpread(int arg1, string arg2) { /.../ }
89+
}
9090

91-
public class MyInvalidDirective : GraphDirective
92-
{
93-
[DirectiveLocations(DirectiveLocation.FIELD)]
94-
// highlight-next-line
95-
public IGraphActionResult ExecuteField(int arg1, int arg2) { /.../ }
96-
97-
// method parameters MUST match for all directive action methods.
98-
[DirectiveLocations(DirectiveLocation.FRAGMENT_SPREAD)]
99-
// highlight-next-line
100-
public IGraphActionResult ExecuteFragSpread(int arg1, string arg2) { /.../ }
101-
}
91+
public class MyInvalidDirective : GraphDirective
92+
{
93+
[DirectiveLocations(DirectiveLocation.FIELD)]
94+
// highlight-next-line
95+
public IGraphActionResult ExecuteField(int arg1, int arg2) { /.../ }
96+
97+
// method parameters MUST match for all directive action methods.
98+
[DirectiveLocations(DirectiveLocation.FRAGMENT_SPREAD)]
99+
// highlight-next-line
100+
public IGraphActionResult ExecuteFragSpread(int arg1, string arg2) { /.../ }
101+
}
102102
```
103103

104104
:::info
@@ -109,25 +109,25 @@ Directives may contain input arguments just like fields. However, its important
109109

110110
(_**a.k.a. Operation Directives**_)
111111

112-
Execution Directives are applied to query documents and executed only on single request in which they are encountered.
112+
Execution Directives are applied to query documents and executed only on the request in which they are encountered.
113113

114114
### Example: @include
115115

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

118118
```csharp
119-
[GraphType("include")]
120-
public sealed class IncludeDirective : GraphDirective
119+
[GraphType("include")]
120+
public sealed class IncludeDirective : GraphDirective
121+
{
122+
[DirectiveLocations(DirectiveLocation.FIELD | DirectiveLocation.FRAGMENT_SPREAD | DirectiveLocation.INLINE_FRAGMENT)]
123+
public IGraphActionResult Execute([FromGraphQL("if")] bool ifArgument)
121124
{
122-
[DirectiveLocations(DirectiveLocation.FIELD | DirectiveLocation.FRAGMENT_SPREAD | DirectiveLocation.INLINE_FRAGMENT)]
123-
public IGraphActionResult Execute([FromGraphQL("if")] bool ifArgument)
124-
{
125-
if (this.DirectiveTarget is IIncludeableDocumentPart idp)
126-
idp.IsIncluded = ifArgument;
125+
if (this.DirectiveTarget is IIncludeableDocumentPart idp)
126+
idp.IsIncluded = ifArgument;
127127

128-
return this.Ok();
129-
}
128+
return this.Ok();
130129
}
130+
}
131131
```
132132

133133
This Directive:
@@ -233,35 +233,35 @@ Type System directives are applied to schema items and executed at start up whil
233233
This directive will extend the resolver of a field, as its declared **in the schema**, to turn any strings into lower case letters.
234234

235235
```csharp title="Example: ToLowerDirective.cs"
236-
public class ToLowerDirective : GraphDirective
236+
public class ToLowerDirective : GraphDirective
237+
{
238+
[DirectiveLocations(DirectiveLocation.FIELD_DEFINITION)]
239+
public IGraphActionResult Execute()
237240
{
238-
[DirectiveLocations(DirectiveLocation.FIELD_DEFINITION)]
239-
public IGraphActionResult Execute()
241+
// ensure we are working with a graph field definition and that it returns a string
242+
if (this.DirectiveTarget is IGraphField field)
240243
{
241-
// ensure we are working with a graph field definition and that it returns a string
242-
if (this.DirectiveTarget is IGraphField field)
243-
{
244-
// ObjectType represents the .NET Type of the data returned by the field
245-
if (field.ObjectType != typeof(string))
246-
throw new Exception("This directive can only be applied to string fields");
247-
248-
// update the resolver to execute the orignal
249-
// resolver then apply lower casing to the string result
250-
var resolver = field.Resolver.Extend(ConvertToLower);
251-
field.UpdateResolver(resolver);
252-
}
253-
254-
return this.Ok();
244+
// ObjectType represents the .NET Type of the data returned by the field
245+
if (field.ObjectType != typeof(string))
246+
throw new Exception("This directive can only be applied to string fields");
247+
248+
// update the resolver to execute the orignal
249+
// resolver then apply lower casing to the string result
250+
var resolver = field.Resolver.Extend(ConvertToLower);
251+
field.UpdateResolver(resolver);
255252
}
256253

257-
private static Task ConvertToLower(FieldResolutionContext context, CancellationToken token)
258-
{
259-
if (context.Result is string)
260-
context.Result = context.Result?.ToString().ToLower();
254+
return this.Ok();
255+
}
261256

262-
return Task.CompletedTask;
263-
}
257+
private static Task ConvertToLower(FieldResolutionContext context, CancellationToken token)
258+
{
259+
if (context.Result is string)
260+
context.Result = context.Result?.ToString().ToLower();
261+
262+
return Task.CompletedTask;
264263
}
264+
}
265265
```
266266

267267
This Directive:
@@ -280,25 +280,25 @@ This Directive:
280280
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.
281281

282282
```csharp
283-
public sealed class DeprecatedDirective : GraphDirective
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")
284287
{
285-
[DirectiveLocations(DirectiveLocation.FIELD_DEFINITION | DirectiveLocation.ENUM_VALUE)]
286-
public IGraphActionResult Execute([FromGraphQL("reason")] string reason = "No longer supported")
288+
if (this.DirectiveTarget is IGraphField field)
287289
{
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();
290+
field.IsDeprecated = true;
291+
field.DeprecationReason = reason;
300292
}
293+
else if (this.DirectiveTarget is IEnumValue enumValue)
294+
{
295+
enumValue.IsDeprecated = true;
296+
enumValue.DeprecationReason = reason;
297+
}
298+
299+
return this.Ok();
301300
}
301+
}
302302
```
303303

304304
This Directive:
@@ -350,9 +350,7 @@ Arguments added to the apply directive attribute will be passed to the directive
350350
```csharp title="Applying Directive Arguments"
351351
public class Person
352352
{
353-
[ApplyDirective(
354-
"deprecated",
355-
"Names don't matter")]
353+
[ApplyDirective("deprecated", "Names don't matter")]
356354
public string Name{ get; set; }
357355
}
358356
```
@@ -450,6 +448,12 @@ services.AddGraphQL(o => {
450448
});
451449
```
452450

451+
```graphql title="Person Type Definition"
452+
type Person @scanItem("medium") @scanItem("high") {
453+
name: String
454+
}
455+
```
456+
453457
### Understanding the Type System
454458

455459
GraphQL ASP.NET builds your schema and all of its types from your controllers and objects. In general, this is done behind the scenes and you do not need to interact with it. However, when applying type system directives you are affecting the generated schema and need to understand the various parts of it. If you have a question don't be afraid to ask on [github](https://github.com/graphql-aspnet/graphql-aspnet).

0 commit comments

Comments
 (0)