mirumee / ariadne

Python library for implementing GraphQL servers using schema-first approach.
https://ariadnegraphql.org
BSD 3-Clause "New" or "Revised" License
2.19k stars 179 forks source link

Directives do not get reported through _service.sdl introspection query ---> subgraph cannot be federated #736

Open magicmark opened 2 years ago

magicmark commented 2 years ago

Apollo federation uses the following introspection query when glueing together subgraphs: https://github.com/apollographql/rover/blob/83d99ad2c707a5da4e3d48593af7b22a51d6d07d/crates/rover-client/src/operations/subgraph/introspect/introspect_query.graphql#L1-L6

query SubgraphIntrospectQuery {
    # eslint-disable-next-line
    _service {
        sdl
    }
}

In our subgraph, we define a schema only, non-executable @lint directive. If I do an introspection for directives directly, it shows up, with all the other directives:

Screen Shot 2021-12-03 at 11 10 13 PM

But if I run the introspection query apollo rover is using (@apollo/rover@0.4.1):

Screen Shot 2021-12-03 at 11 09 37 PM

^ That's just some random test schema, but cruicially - no directives in the output.

This ultimately leads to the following:

error[E029]: Encountered 1 build error while trying to build a supergraph.

Caused by:
    Encountered 1 build error while trying to build the supergraph.
    UNKNOWN: [@lint] -> Custom directives must be implemented in every service. The following services do not implement the @lint directive: i18n_strings.

I'll keep poking around, it's possible i'm missing a trick here somewhere, or need to upgrade things, but writing this down as a starting point. Thanks!

magicmark commented 2 years ago

super gross, i'm sure there's a better answer out there, but in case not...here's what i came up with

def fix_directives_in_sdl_introspection(handler, registry):
    def fix_sdl(request):
        response = handler(request)

        if (
            request.content_type != "application/json"
            or request.json_body["operationName"] != "SubgraphIntrospectQuery"
        ):
            # skip if it's not an _service.sdl introspection query.
            return response

        assert("_service" in request.json_body["query"], "Expected Query._service.sdl as the introspection query for SubgraphIntrospectQuery")
        assert("sdl" in request.json_body["query"], "Expected Query._service.sdl as the introspection query for SubgraphIntrospectQuery")

        json_body = response.json_body
        json_body["data"]["_service"]["sdl"] += """
            directive @lint(disable: [String!] = [], enable: [String!] = []) on QUERY | MUTATION | SUBSCRIPTION | FIELD | FRAGMENT_DEFINITION | FRAGMENT_SPREAD | INLINE_FRAGMENT | SCHEMA | SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
        """
        response.json_body = json_body
        return response

    return fix_sdl

(if i haven't just missed a config setting, then the "real" answer would be to PR and get ariadne to return this via the sdl introspection field normally)

lschmierer commented 2 years ago

I am running into this issue as well.

@magicmark I am quite new to Ariadne and ASGI in general, where do I apply the fix_directives_in_sdl_introspection function you provided?

This is how I am setting up my serivice:

...

schema = make_federated_schema(type_defs, query, directives={"isNotAuthenticated": IsNotAuthenticatedDirective, "hasRole": HasRoleDirective})
app = GraphQL(schema)
lschmierer commented 2 years ago

As far as I can tell, while Apollo Gateway is fine with stripping TypeSystemDirectiveLocation directive definitions, ExecutableDirectiveLocation directive definitions are expected to be returned in _service { sdl }.

A solution would be to change https://github.com/mirumee/ariadne/blob/master/ariadne/contrib/federation/utils.py#L60 to only purge TypeSystemDirectives and keep ExecutableDirectives or even delete the line completely. In my experience, the current implementation of Apollo Gateway is not bothered by _service { sdl } containing TypeSystemDirectives.