Skip to content
This repository has been archived by the owner on Oct 21, 2022. It is now read-only.

Error: Directive "external" may not be used on ARGUMENT_DEFINITION. #26

Closed
fabianriewe opened this issue Dec 3, 2019 · 12 comments
Closed

Comments

@fabianriewe
Copy link

fabianriewe commented Dec 3, 2019

Hey, I just upgraded my project to a new version. I was using version 0.0.2 and everything was working fine. By upgrading to version >=0.0.4. I got the following errors in my Apollo gateway:

(node:3328) UnhandledPromiseRejectionWarning: GraphQLSchemaValidationError: Directive "external" may not be used on ARGUMENT_DEFINITION.

Directive "external" may not be used on ARGUMENT_DEFINITION.

[doc] ExamResult.id -> Found extraneous @external directive. @external cannot be used on base types.

[payment] Plan.id -> Found extraneous @external directive. @external cannot be used on base types.

Directive "external" may not be used on ARGUMENT_DEFINITION.

Both of my types are not using any external arguments.

Do you have any ideas on this?

@erebus1
Copy link
Collaborator

erebus1 commented Dec 3, 2019

Thanks for reporting!
Seem it is an issue with regexp
Can you please share your schema definition, so that I'll be able to reproduce an issue?
At least [doc] ExamResult [payment] Plan and types which has @external on FIELD_DEFINITION

@fabianriewe
Copy link
Author

fabianriewe commented Dec 3, 2019

Sure! ExamResult type has an external user. But other type haven them as well.

Plan.py in payment/types/plan.py

from graphene_federation import key
from graphene_mongo import MongoengineObjectType

from src.models import PlanModel


@key('id')
class Plan(MongoengineObjectType):
    class Meta:
        model = PlanModel

ExamResult.py in doc/types/exam_result.py

import graphene
from graphene_mongo import MongoengineObjectType

from graphene_federation import key
from src.models import ExamResultModel
from .exam import Exam
from .material import Material
from .topic import Topic
from .user import User


@key('id')
class ExamResult(MongoengineObjectType):
    exam = graphene.Field(Exam)
    material = graphene.Field(Material)
    gaps = graphene.List(Topic)
    user = graphene.Field(User)

    class Meta:
        model = ExamResultModel

external User in doc/types/user.py

import graphene

from graphene_federation import extend, external


@extend('id')
class User(graphene.ObjectType):
    id = external(graphene.ID())

I need to overwrite external relations, because by default MongoengineObjectType sets them as internal.

@firaskafri
Copy link

@erebus1 We are facing the same issue. Any ideas or pointers?

@firaskafri
Copy link

@erebus1

After a lot of investigation, it turns out that the regex is marking wrong occurrences when you try to use the external directive on a DjangoObjectType's field.

@erebus1
Copy link
Collaborator

erebus1 commented Dec 26, 2019

@firaskafri Thanks for an investigation!
I'll try to fix it on weekends

@erebus1
Copy link
Collaborator

erebus1 commented Dec 29, 2019

@firaskafri Can you please provide an example when you're using external on DjangoObjectType's field

As I understand if the field is of type DjangoObjectType - then this means this is a base type and should not be marked as external

Or you mean that this type is defined in another service?

@devopsjosh
Copy link

devopsjosh commented Jan 7, 2020

I am experiencing a similar issue but receiving Directive "external" may not be used on INPUT_FIELD_DEFINITION.

I have a Django model that stores an id and a set of objects (ManyToMany). This model is an extension of a model in another service where the id's match. The schema is setup as

Service 1

@key(fields="id")
class MyObjectNode(DjangoObjectType):
    class Meta:
        model = MyObject
        interfaces = (RelayNode,)

Service 2

@extends(fields="id")
class MyObjectNode(DjangoObjectType):
    id = external(ID(required=True))
    related_object_set = DjangoConnectionField(RelatedObjectNode)
    
    def resolve_related_object_set(self, info):
        return self.related_object_set.all()

    class Meta:
        model = MyObject
        interfaces = (RelayNode,)

I have two mutation classes that adds/removes RelatedObjects to/from the extended MyObject class. It gives the above error when using more than one mutation that returns a MyObjectNode.

class AddRelatedObjectMutation(graphene.Mutation):
    class Arguments:
        id = graphene.UUID(required=True)
        related_objects = graphene.List(graphene.UUID, required=True)

    my_object = graphene.Field(MyObjectNode)
    errors = graphene.List(
        ErrorType, description="May contain more than one error for same field."
    )

    def mutate(self, info, id, related_objects):
        my_object = MyObject.objects.get(pk=id)
        my_object.related_objects.add(*related_objects)

        return AddRelatedObject(my_object=my_object, errors=None)


class RemoveRelatedObjectMutation(graphene.Mutation):
    class Arguments:
        id = graphene.UUID(required=True)
        related_objects = graphene.List(graphene.UUID, required=True)

    my_object = graphene.Field(MyObjectNode)
    errors = graphene.List(
        ErrorType, description="May contain more than one error for same field."
    )

    def mutate(self, info, id, related_objects):
        my_object = MyObject.objects.get(pk=id)
        my_object.related_objects.remove(*related_objects)

        return RemoveRelatedObjectMutation(my_object=my_object, errors=None)


class Mutation(graphene.ObjectType):
    add_related_objects = AddRelatedObjectMutation.Field()
    remove_related_objects = RemoveRelatedMutation.Field()

If I swap out my_object = graphene.Field(MyObjectNode) on the second mutation for something like ok = graphene.Boolean(), my Apollo Gateway will work. Doesn't seem to like the mutations having the same NodeType returned.

Edit: Simply renaming the RemoveRelatedObjectMutation class to DetachRelatedObjectMutation, allowed it to work. I inspected and compared the produced schema from get_sdl() in service.py of both mutation type definitions. The one that doesn't work has @external directive added to the input definition on the mutation, whereas the other does not.

@firaskafri
Copy link

@erebus1 True in our case the type is defined in another service.
But the issue is generic and goes beyond base types (of any type, not only DjangoObjectType's) the regex identifying the external fields when a type is implementing "Node".

@devopsjosh
Copy link

devopsjosh commented Jan 8, 2020

Schema:

 type RemoveRelatedObjectMutation{   myObject: MyObject   errors: [ErrorType] }  input MyObjectRelatedInput {   id: UUID!   tasks: [UUID]! }

After checking what the regex matches on my mutation schema, I noticed it will match the following:

MyObject   errors: [ErrorType] }  input MyObjectRelatedInput {   id: UUID!

And will proceed to add @external after the id definition.

Perhaps looking for @key or type\s{entity_name} when marking it external would help.

devopsjosh referenced this issue in devopsjosh/graphene-federation Jan 8, 2020
@erebus1
Copy link
Collaborator

erebus1 commented Jan 9, 2020

Guys, thanks a lot for your help with an investigation!
@jtesch I've added your fix with tests and published new version

@jtesch @firaskafri @fabianriewe Can you please test 0.0.6 version in your projects, and report if any issue with external is still there?

@erebus1
Copy link
Collaborator

erebus1 commented Jan 18, 2020

I close an issue
Feel free to reopen it if you'll see an issue again

@erebus1 erebus1 closed this as completed Jan 18, 2020
@mahdyhamad
Copy link

mahdyhamad commented Apr 20, 2021

Facing this error when using id field in filter_fields
More details:
It happened when an id is in filter_fields of a DjangoObjectType Node.

This Node is inside an extended Node as a DjangoFilterConnectionField.
This is happening on a specific node only and when adding the 'id' field in filter_fields.
If filter_fields = { "id": ["icontains"] } only, this works fine, but when "exact" lookup is added to the list, I have this error.
... that would be filter_fields = { "id": ["exact"] }

A turnaround solution to this problem is to pass the id to the resolver.
Ex:
foo_field = DjangoFilterConnectionField(FooNode, id=graphene.ID(required=False))

If I am not the only one facing this problem when trying to filter, please open this issue

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

No branches or pull requests

5 participants