Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How I can set custom directives to the current field? #18

Closed
dmitrydrynov opened this issue Jul 19, 2021 · 14 comments · Fixed by #37
Closed

How I can set custom directives to the current field? #18

dmitrydrynov opened this issue Jul 19, 2021 · 14 comments · Fixed by #37

Comments

@dmitrydrynov
Copy link

dmitrydrynov commented Jul 19, 2021

Hi guys,
How I can set custom directives to the current field through Javascript code?

I created a custom Directive:

const authDirective = new GraphQLDirective({
    name: 'auth',
    locations: [DirectiveLocation.OBJECT, DirectiveLocation.FIELD_DEFINITION],
    args: ...
})

Added to the schema:

const schema = new GraphQLSchema({
    directives: [authDirective],
    query: new GraphQLObjectType({
        fields: {
            secretData: ...
        }
    })
})

How I can use my directive only for the secretData field?

I tried to add a directives parameter for field definition but it doesn't work:

{
    type: validateWidgetQLType,
    args: validateWidgetQLArgs,
    directives: [
        { name: 'auth', arguments: { requires: 0 }}
    ],
}

Please help!

@jonnydgreen
Copy link
Collaborator

Hello! :) I'm not familiar with defining a schema in this way - but I can lend a hand for sure! When you say it doesn't work, could you clarify what you mean?

  • It doesn't show the auth directive for the secretData field in the instantiated GraphQLSchema?
  • Or mercurius-auth doesn't work correctly for this schema definition (that has the custom directive correctly loaded)?

One good point of reference is the graphql-js typings: https://graphql.org/graphql-js/type/#graphqlobjecttype (GraphQLFieldConfig )

@dmitrydrynov
Copy link
Author

dmitrydrynov commented Jul 19, 2021

Both
When I print the schema as SDL I don't see the @auth directive for the secretData field.

directive @auth(requires: Role = ADMIN) on OBJECT | FIELD_DEFINITION
type Query {
        secretData(x: Int, y: Int): Int
}

In mercurius-auth a code in applyPolicy() is never executed

@jonnydgreen
Copy link
Collaborator

Cool thanks, makes sense that mercurius-auth applyPolicy doesn't run if the directive isn't registered for that field in the schema. Could you provide the schema AST JSON?

@dmitrydrynov
Copy link
Author

dmitrydrynov commented Jul 19, 2021

{
  "kind": "Document",
  "definitions": [
    {
      "kind": "DirectiveDefinition",
      "name": {
        "kind": "Name",
        "value": "auth"
      },
      "arguments": [
        {
          "kind": "InputValueDefinition",
          "name": {
            "kind": "Name",
            "value": "requires"
          },
          "type": {
            "kind": "NamedType",
            "name": {
              "kind": "Name",
              "value": "UserRole"
            }
          },
          "defaultValue": {
            "kind": "EnumValue",
            "value": "GUEST"
          },
          "directives": []
        }
      ],
      "repeatable": false,
      "locations": [
        {
          "kind": "Name",
          "value": "OBJECT"
        },
        {
          "kind": "Name",
          "value": "FIELD_DEFINITION"
        }
      ]
    },
    {
      "kind": "ObjectTypeDefinition",
      "name": {
        "kind": "Name",
        "value": "Mutation"
      },
      "interfaces": [],
      "directives": [],
      "fields": [
        {
          "kind": "FieldDefinition",
          "name": {
            "kind": "Name",
            "value": "validateWidget"
          },
          "arguments": [
            {
              "kind": "InputValueDefinition",
              "name": {
                "kind": "Name",
                "value": "step"
              },
              "type": {
                "kind": "NamedType",
                "name": {
                  "kind": "Name",
                  "value": "String"
                }
              },
              "directives": []
            },
            {
              "kind": "InputValueDefinition",
              "name": {
                "kind": "Name",
                "value": "formData"
              },
              "type": {
                "kind": "NamedType",
                "name": {
                  "kind": "Name",
                  "value": "JSONObject"
                }
              },
              "directives": []
            }
          ],
          "type": {
            "kind": "NamedType",
            "name": {
              "kind": "Name",
              "value": "validateWidgetType"
            }
          },
          "directives": []
        }
      ]
    },
    {
      "kind": "ObjectTypeDefinition",
      "name": {
        "kind": "Name",
        "value": "validateWidgetType"
      },
      "interfaces": [],
      "directives": [],
      "fields": [
        {
          "kind": "FieldDefinition",
          "name": {
            "kind": "Name",
            "value": "step"
          },
          "arguments": [],
          "type": {
            "kind": "NonNullType",
            "type": {
              "kind": "NamedType",
              "name": {
                "kind": "Name",
                "value": "String"
              }
            }
          },
          "directives": []
        },
        {
          "kind": "FieldDefinition",
          "name": {
            "kind": "Name",
            "value": "nextStep"
          },
          "arguments": [],
          "type": {
            "kind": "NamedType",
            "name": {
              "kind": "Name",
              "value": "String"
            }
          },
          "directives": []
        },
        {
          "kind": "FieldDefinition",
          "name": {
            "kind": "Name",
            "value": "validated"
          },
          "arguments": [],
          "type": {
            "kind": "NonNullType",
            "type": {
              "kind": "NamedType",
              "name": {
                "kind": "Name",
                "value": "Boolean"
              }
            }
          },
          "directives": []
        },
        {
          "kind": "FieldDefinition",
          "name": {
            "kind": "Name",
            "value": "error"
          },
          "arguments": [],
          "type": {
            "kind": "NamedType",
            "name": {
              "kind": "Name",
              "value": "String"
            }
          },
          "directives": []
        },
        {
          "kind": "FieldDefinition",
          "name": {
            "kind": "Name",
            "value": "token"
          },
          "arguments": [],
          "type": {
            "kind": "NamedType",
            "name": {
              "kind": "Name",
              "value": "String"
            }
          },
          "directives": []
        },
        {
          "kind": "FieldDefinition",
          "name": {
            "kind": "Name",
            "value": "data"
          },
          "arguments": [],
          "type": {
            "kind": "NamedType",
            "name": {
              "kind": "Name",
              "value": "JSONObject"
            }
          },
          "directives": []
        }
      ]
    },
    {
      "kind": "EnumTypeDefinition",
      "name": {
        "kind": "Name",
        "value": "UserRole"
      },
      "directives": [],
      "values": [
        {
          "kind": "EnumValueDefinition",
          "name": {
            "kind": "Name",
            "value": "ADMIN"
          },
          "directives": []
        },
        {
          "kind": "EnumValueDefinition",
          "name": {
            "kind": "Name",
            "value": "CLIENT"
          },
          "directives": []
        },
        {
          "kind": "EnumValueDefinition",
          "name": {
            "kind": "Name",
            "value": "USER"
          },
          "directives": []
        },
        {
          "kind": "EnumValueDefinition",
          "name": {
            "kind": "Name",
            "value": "GUEST"
          },
          "directives": []
        }
      ]
    }
  ]
}

@jonnydgreen
Copy link
Collaborator

you don't happen to have the fully resolved AST do you? As name is coming up as [Object] etc. Something like: JSON.stringify(schemaAST, null, 2) should do nicely!

@dmitrydrynov
Copy link
Author

updated above

@jonnydgreen
Copy link
Collaborator

jonnydgreen commented Jul 19, 2021

Cool, thanks - I can take a closer look this evening :) In the meantime, I can point you to this thread, which is a good read on the subject: graphql/graphql-js#1343 - from an initial glance, I'm not sure if it's currently possible to define them in this way (this is backed up by the fact I can see directive definitions but no usages, so I don't think it's a mercurius-auth issue)

@jonnydgreen
Copy link
Collaborator

This is also another excellent resource: https://www.graphql-tools.com/docs/schema-directives/#what-about-code-first-schemas

@jonnydgreen
Copy link
Collaborator

Just taken a closer look and based on the thread in the issue I linked, it currently looks like directives cannot be defined in this way (this is reflected in the AST omitted, as it doesn't contain any directive usage). Have you considered any alternative approaches?

@mcollina
Copy link
Contributor

Can we support defining the auth behavior outside of the schema with a config object?

@jonnydgreen
Copy link
Collaborator

Great idea! In theory, yes, as I think it would be a case of an extra lookup within the config object as well as the schema (when traversing the schema tree to (re-)define the field resolvers for protected fields)? And if we get a match, we run applyPolicy as normal

The only issue I can see is when we have a field that already contains a matching directive, maybe we just run the config applyPolicy first and then the schema applyPolicy? Or even support one mode or the other exclusively! What do you think?

@mcollina
Copy link
Contributor

I would just support only one mode, via an option.

@jonnydgreen
Copy link
Collaborator

I would just support only one mode, via an option.

Agreed - will certainly remove any edges cases that way

@dmitrydrynov would you like to implement this feature?

@jonnydgreen
Copy link
Collaborator

@dmitrydrynov would you like to implement this feature?

Hi @dmitrydrynov :) not sure if you missed my comment - would this be something you would be interested in contributing to? I was thinking of starting the implementation for this when I'm next free, but didn't want to step on your toes in case you had started it or wanted to do this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants