Directives

Specification

Directives are created as instance of DirectiveType. When adding to target instance of DirectiveInstance is used. It contains a reference back to the original type.

Following directives are provided as static properties of DirectiveType

Skip = new DirectiveType(
            "skip",
            new[]
            {
                DirectiveLocation.FIELD,
                DirectiveLocation.FRAGMENT_SPREAD,
                DirectiveLocation.INLINE_FRAGMENT
            },
            new Args
            {
                {"if", ScalarType.NonNullBoolean}
            })

Include = new DirectiveType(
            "include",
            new[]
            {
                DirectiveLocation.FIELD,
                DirectiveLocation.FRAGMENT_SPREAD,
                DirectiveLocation.INLINE_FRAGMENT
            },
            new Args()
            {
                {"if", ScalarType.NonNullBoolean}
            })

Deprecated = new DirectiveType(
            "deprecated",
            new[]
            {
                DirectiveLocation.FIELD_DEFINITION,
                DirectiveLocation.ENUM_VALUE
            },
            new Args()
            {
                {"reason", ScalarType.String}
            })

Create custom directive

Create simple DirectiveType and apply instance of it to a field and modify resolver logic to execute custom logic if it has the directive present.

This example will require a role from user when trying to resolve a field value


        [Fact]
        public async Task Authorize_field_directive_sdl()
        {
            /* Given */
            var builder = new SchemaBuilder()
                .Sdl(Parser.ParseDocument(@"
                directive @authorize(
                    role: String =""user""
                ) on FIELD_DEFINITION

                type Query {
                    requiresAdmin: String @authorize(role:""admin"")
                    requiresUser: String @authorize
                }

                schema {
                    query: Query
                }
                "));

            var resolvers = new ObjectTypeMap
            {
                {
                    "Query", new FieldResolversMap
                    {
                        {"requiresAdmin", context => new ValueTask<IResolverResult>(Resolve.As("Hello Admin!"))},
                        {"requiresUser", context => new ValueTask<IResolverResult>(Resolve.As("Hello User!"))}
                    }
                }
            };

            // mock user and user store
            var user = new ClaimsPrincipal(new ClaimsIdentity(new []
            {
                new Claim("role", "user"),
            }));

            ClaimsPrincipal FetchUser(int id) => user;

            /* When */
            var schema = SchemaTools.MakeExecutableSchema(
                builder,
                resolvers,
                directives: new Dictionary<string, CreateDirectiveVisitor>
                {
                    // register directive visitor to be used when authorizeType.Name present
                    ["authorize"] = AuthorizeVisitor(FetchUser)
                });

            var result = await Executor.ExecuteAsync(new ExecutionOptions
            {
                Document = Parser.ParseDocument(@"{ requiresAdmin requiresUser }"),
                Schema = schema
            });

            /* Then */
            result.ShouldMatchJson(@"
                  {
                  ""data"": {
                    ""requiresAdmin"": null,
                    ""requiresUser"": ""Hello User!""
                  },
                  ""errors"": [
                    {
                      ""message"": ""requires admin role. "",
                      ""locations"": [
                        {
                          ""end"": 28,
                          ""start"": 2
                        }
                      ],
                      ""path"": [
                        ""requiresAdmin""
                      ],
                      ""extensions"": {
                        ""code"": ""EXCEPTION""
                      }
                    }
                  ]
                }
                ");
        }

DirectiveVisitor applies the actual middleware to the resolver chain

        public static CreateDirectiveVisitor AuthorizeVisitor(Func<int, ClaimsPrincipal> fetchUser)
        {
            return builder => new DirectiveVisitor
            {
                FieldDefinition = (directive, fieldDefinition) =>
                {
                    return fieldDefinition.WithResolver(resolver => resolver.Use((context, next) =>
                    {
                        var requiredRole = directive.GetArgument<string>("role");
                        var user = fetchUser(42);
                        
                        if (!user.HasClaim("role", requiredRole))
                            throw new Exception(
                                "requires admin role. "
                            );

                        return next(context);
                    }).Run(fieldDefinition.Resolver));
                }
            };
        }