Skip to content

Commit d97a8b1

Browse files
committed
added security section and additional examples to directives
1 parent edb5826 commit d97a8b1

File tree

1 file changed

+98
-19
lines changed

1 file changed

+98
-19
lines changed

docs/advanced/directives.md

Lines changed: 98 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ Directives can't directly return data or resolve a field. They can only indicate
7777
* `this.Cancel()`:
7878
* The directive failed and the schema should not be generated or the target field should be dropped.
7979

80-
> throwing an exception within an action method of a directive will cause the current query to fail completely. Use `this.Cancel()` to only drop the single targeted field.
80+
> Throwing an exception within an action method of a directive will cause the current query to fail completely. Use `this.Cancel()` to discard only the currently resolving field. Normal nullability validation rules still apply.
8181
8282
### Directive Target
8383
The `this.DirectiveTarget` property will contain either an `ISchemaItem` for type system directives or the resolved field value for execution directives. This value is useful in performing additional operations such as extending a field resolver during schema generation or taking further action against a resolved field.
@@ -176,7 +176,7 @@ It is recommended that your directives act independently and be self contained.
176176

177177

178178
## Type System Directives
179-
### Example: @ToUpper
179+
### Example: @toUpper
180180

181181
This directive will extend the resolver of a field to turn any strings into upper case letters.
182182

@@ -215,13 +215,44 @@ This directive will extend the resolver of a field to turn any strings into uppe
215215

216216
This Directive:
217217

218-
* Targets a FIELD_DEFINITION and can be applied to any field of any type.
218+
* Targets any FIELD_DEFINITION.
219219
* Ensures that the target field returns returns a string.
220220
* Extends the field's resolver to convert the result to an upper case string.
221221
* The directive is executed once per field its applied to when the schema is created. The extension method is executed on every field resolution.
222222
* If an exception is thrown the schema will fail to create and the server will not start.
223223
* if the action method returns a cancel result (e.g. `this.Cancel()`) the schema will fail to create and the server will not start.
224224

225+
### Example: @deprecated
226+
227+
```csharp
228+
public sealed class DeprecatedDirective : GraphDirective
229+
{
230+
[DirectiveLocations(DirectiveLocation.FIELD_DEFINITION | DirectiveLocation.ENUM_VALUE)]
231+
public IGraphActionResult Execute([FromGraphQL("reason")] string reason = "No longer supported")
232+
{
233+
if (this.DirectiveTarget is IGraphField field)
234+
{
235+
field.IsDeprecated = true;
236+
field.DeprecationReason = reason;
237+
}
238+
else if (this.DirectiveTarget is IEnumValue enumValue)
239+
{
240+
enumValue.IsDeprecated = true;
241+
enumValue.DeprecationReason = reason;
242+
}
243+
244+
return this.Ok();
245+
}
246+
}
247+
```
248+
249+
250+
This Directive:
251+
252+
* Targets a FIELD_DEFINITION or ENUM_VALUE.
253+
* Marks the field or enum value as deprecated and attaches the provided deprecation reason
254+
* The directive is executed once per field and enum value its applied to when the schema is created.
255+
225256
### Applying Type System Directives
226257

227258
#### Using the `[ApplyDirective]` attribute
@@ -284,7 +315,7 @@ type Person @monitor {
284315

285316
<br/>
286317
<br/>
287-
**Adding Arguments**
318+
**Adding Arguments with [ApplyDirective]**
288319

289320
Arguments added to the apply directive attribute will be passed to the directive in the order they are encountered. The supplied values must be coercable into the expected data types for an input parameters.
290321

@@ -293,9 +324,11 @@ Arguments added to the apply directive attribute will be passed to the directive
293324

294325
```csharp
295326
// Person.cs
296-
[ApplyDirective("monitor", "trace")]
297327
public class Person
298328
{
329+
[ApplyDirective(
330+
"deprecated",
331+
"Names don't matter")]
299332
public string Name{ get; set; }
300333
}
301334
```
@@ -305,21 +338,23 @@ public class Person
305338

306339
```javascript
307340
// GraphQL Type Definition Equivilant
308-
type Person @monitor(level: "trace") {
309-
name: String
341+
type Person {
342+
name: String @deprecated("Names don't matter")
310343
}
311344
```
312345

313346
</div>
314347
</div>
315348

316-
<br/>
317349
<br/>
318350

319351
#### Using Schema Options
320352

321353
Alternatively, instead of using attributes to apply directives you can apply directives during schema configuration:
322354

355+
<div class="sideBySideCode hljs">
356+
<div>
357+
323358
```csharp
324359
// startup.cs
325360
public void ConfigureServices(IServiceCollection services)
@@ -329,21 +364,41 @@ public void ConfigureServices(IServiceCollection services)
329364
services.AddGraphQL(options =>
330365
{
331366
options.AddGraphType<Person>();
367+
368+
// mark Person.Name as deprecated
332369
options.ApplyDirective("monitor")
333-
.ToItems(schemaItem => schemaItem is IObjectGraphType ogt &&
334-
ogt.ObjectType == typeof(Person));
370+
.ToItems(schemaItem =>
371+
schemaItem.IsObjectGraphType<Person>());
335372
}
373+
}
374+
```
336375

376+
</div>
377+
<div>
378+
379+
```javascript
380+
// GraphQL Type Definition Equivilant
381+
type Person @monitor {
382+
name: String
337383
}
338384
```
339385

340-
> The `ToItems` filter can be applied multiple times. A schema item must match all filter criteria in order for the directive to be applied.
386+
</div>
387+
</div>
388+
389+
<br/>
390+
391+
> The `ToItems` filter can be invoked multiple times. A schema item must match all filter criteria in order for the directive to be applied.
341392

342-
**Adding arguments**
393+
**Adding arguments via .ApplyDirective()**
343394

344395
Adding Arguments via schema options is a lot more flexible than via the apply directive attribute. Use the `.WithArguments` method to supply either a static set of arguments for all matched schema items
345396
or a `Func<ISchemaItem, object[]>` that returns a collection of any parameters you want on a per item basis.
346397

398+
399+
<div class="sideBySideCode hljs">
400+
<div>
401+
347402
```csharp
348403
// startup.cs
349404
public void ConfigureServices(IServiceCollection services)
@@ -353,25 +408,49 @@ public void ConfigureServices(IServiceCollection services)
353408
services.AddGraphQL(options =>
354409
{
355410
options.AddGraphType<Person>();
356-
options.ApplyDirective("monitor")
357-
.WithArguments("trace")
358-
.ToItems(schemaItem => schemaItem is IObjectGraphType ogt &&
359-
ogt.ObjectType == typeof(Person));
411+
options.ApplyDirective("deprecated")
412+
.WithArguments("Names don't matter")
413+
.ToItems(schemaItem =>
414+
schemaItem.IsGraphField<Person>("name"));
360415
}
416+
}
417+
```
418+
361419

420+
</div>
421+
<div>
422+
423+
```javascript
424+
// GraphQL Type Definition Equivilant
425+
type Person {
426+
name: String @deprecated("Names don't matter")
362427
}
363428
```
429+
430+
</div>
431+
</div>
432+
433+
364434
<br/>
365435

366436
## Directives as Services
367-
Directives are invoked as services through your DI container when they are executed. When you add types to your schema during its initial configuration, GraphQL ASP.NET will automatically register any directives it finds as services in your `IServiceCollection` instance. However, there are times when it cannot do this, such as when you apply a directive by its name. These late bound directives may still be discoverable and graphql will attempt to add them to your schema whenever it can. However, it may do this after the opportunity to register them with the DI container has passed.
437+
Directives are invoked as services through your DI container when they are executed. When you add types to your schema during its initial configuration, GraphQL ASP.NET will automatically register any directives it finds attached to your objects and properties as services in your `IServiceCollection` instance. However, there are times when it cannot do this, such as when you apply a directive by its string declared name. These late-bound directives may still be discoverable later and graphql will attempt to add them to your schema whenever it can. However, it may do this after the opportunity to register them with the DI container has passed.
368438

369-
When this occurs, if your directive contains a public, parameterless constructor it will still instantiate and use your directive as normal. If the directive contains dependencies in the constructor that it can't resolve, execution of that directive will fail and an exception will be thrown. To be safe, make sure to add any directives you may use to your schema during the `.AddGraphQL()` configuration. Directives are discoverable and will be included via the `options.AddAssembly()` helper method.
439+
When this occurs, if your directive contains a public, parameterless constructor graphql will still instantiate and use your directive as normal. If the directive contains dependencies in the constructor that it can't resolve, execution of that directive will fail and an exception will be thrown. To be safe, make sure to add any directives you may use to your schema during the `.AddGraphQL()` configuration method. Directives are directly discoverable and will be included via the `options.AddAssembly()` helper method as well.
370440

371441
The benefit of ensuring your directives are part of your `IServiceCollection` should be apparent:
372-
* The directive instance creation will obey lifetime scopes (transient, scoped, singleton).
442+
* The directive instance will obey lifetime scopes (transient, scoped, singleton).
373443
* The directive can be instantiated with any dependencies or services you wish; making for a much richer experience.
374444

445+
## Directive Security
446+
Directives are not considered a layer of security by themselves. Instead, they are invoked within the security context of their applied target:
447+
448+
* **Execution Directives** - Execute in the same context as the field to which they are applied. If the requestor can resolve the field, they can also execute the directives attached to that field.
449+
450+
* **Type System Directives** - Are implicitly trusted and executed without a `ClaimsPrincipal` while the schema is being built. No additional security is applied to type system directives.
451+
452+
> WARNING: Only use type system directives that you trust. They will always be executed when applied to one or more schema items.
453+
375454
## Understanding the Type System
376455
GraphQL ASP.NET builds your schema and all of its types from your controllers and objects. In general, you do not need to interact with it, however; when applying type system directives you are affecting the final generated schema at run time, making changes as you see fit. When doing this you are forced to interact with the internal type system.
377456

0 commit comments

Comments
 (0)