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

Federation SDL is invalid for non-Apollo tools #252

Open
jturkel opened this issue Jun 15, 2023 · 3 comments
Open

Federation SDL is invalid for non-Apollo tools #252

jturkel opened this issue Jun 15, 2023 · 3 comments

Comments

@jturkel
Copy link
Contributor

jturkel commented Jun 15, 2023

I'm not sure if this is intentional but the generated federation SDL is invalid for non-Apollo tools because it doesn't include the federation directive definitions. Here's a minimal reproducible test case:

require 'bundler/inline'

gemfile do
  source 'https://rubygems.org'
  gem 'graphql', '2.0.22'
  gem 'apollo-federation', '3.8.0'
end

class ProductType < GraphQL::Schema::Object
  include ApolloFederation::Object
  key fields: :upc

  field :upc, String, null: false
end

class QueryType < GraphQL::Schema::Object
  field :product, ProductType, null: false
end

class ProductSchema < GraphQL::Schema
  include ApolloFederation::Schema
  federation version: '2.0'
  query QueryType
end

sdl = ProductSchema.federation_sdl
puts sdl
GraphQL::Schema.from_definition(sdl)

This fails with the following exception:

/Users/jturkel/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/graphql-2.0.22/lib/graphql/schema/build_from_definition.rb:244:in `block in prepare_directives': No definition for @federation__key on Product at 4:1 (ArgumentError)

I've confirmed graphql-js behaves the same way:

const { buildSchema, printSchema, validateSchema } = require('graphql');

const schemaDefinition = `
# Uncomment the following the schema will be valid
# scalar federation__FieldSet
# scalar link__Import
# enum link__Purpose {
#   SECURITY
#   EXECUTION
# }
# directive @link(url: String!, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA
# directive @federation__key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE

extend schema
  @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@inaccessible", "@tag"])

type Product @federation__key(fields: "upc") {
  upc: String!
}

type Query {
  product: Product!
}
`;

const schema = buildSchema(schemaDefinition);
const validationErrors = validateSchema(schema);
if (validationErrors.length > 0) {
  console.error('Schema validation errors:');
  validationErrors.forEach((error) => console.error(error.message));
} else {
  console.log('Schema is valid.');
}

This fails with the following exception:

/Users/jturkel/code/bug_test_cases/node_modules/graphql/validation/validate.js:135
    throw new Error(errors.map((error) => error.message).join('\n\n'));
    ^

Error: Unknown directive "@link".

Unknown directive "@federation__key".
    at assertValidSDL (/Users/jturkel/code/bug_test_cases/node_modules/graphql/validation/validate.js:135:11)
    at buildASTSchema (/Users/jturkel/code/bug_test_cases/node_modules/graphql/utilities/buildASTSchema.js:44:34)
    at buildSchema (/Users/jturkel/code/bug_test_cases/node_modules/graphql/utilities/buildASTSchema.js:109:10)
    at Object.<anonymous> (/Users/jturkel/code/bug_test_cases/validate_schema.js:26:16)

I've also confirmed that the Apollo tools work fine if the federation directive definitions are included in the SDL.

@grxy
Copy link
Collaborator

grxy commented Jun 20, 2023

Yeah this has been annoying me for a bit, but I've been ignoring it. Does Apollo have any guidance here?

@jturkel
Copy link
Contributor Author

jturkel commented Jun 26, 2023

The Apollo Federation spec says to include all uses of federation-specific directives but there's no mention of the directive definitions, although all of the spec examples omit the directive definitions.

I followed the @apollo/subgraph setup instructions to create a simple test server using the official Apollo libraries:

import gql from 'graphql-tag';
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone'
import { buildSubgraphSchema } from '@apollo/subgraph';

const typeDefs = gql`
  extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@shareable"])

  type Query {
    me: User
  }

  type User @key(fields: "id") {
    id: ID!
    username: String
  }
`;

const resolvers = {};

const server = new ApolloServer({
  schema: buildSubgraphSchema({ typeDefs, resolvers }),
});

const { url } = await startStandaloneServer(server, {
  listen: { port: 4000 },
});

console.log(`🚀  Server ready at: ${url}`);

Querying Query._service.sdl returns a schema with federation directive definitions:

schema
  @link(url: "https://specs.apollo.dev/link/v1.0")
{
  query: Query
}

extend schema
  @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@shareable"])

directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA

directive @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE

directive @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION

directive @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION

directive @federation__external(reason: String) on OBJECT | FIELD_DEFINITION

directive @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION

directive @federation__extends on OBJECT | INTERFACE

directive @shareable on OBJECT | FIELD_DEFINITION

directive @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION

directive @federation__override(from: String!) on FIELD_DEFINITION

type Query {
  me: User
  _entities(representations: [_Any!]!): [_Entity]!
  _service: _Service!
}

type User
  @key(fields: "id")
{
  id: ID!
  username: String
}

enum link__Purpose {
  """
  `SECURITY` features provide metadata necessary to securely resolve fields.
  """
  SECURITY

  """
  `EXECUTION` features provide metadata necessary for operation execution.
  """
  EXECUTION
}

scalar link__Import

scalar federation__FieldSet

scalar _Any

type _Service {
  sdl: String
}

union _Entity = User

So in summary, the Apollo tooling works fine with or without the directive definitions but their reference implementation does include the directives the Query._service.sdl field.

@grxy
Copy link
Collaborator

grxy commented Jul 11, 2023

@jturkel Interesting. Our sdl field calls our federation_sdl helper, which does not include those definitions, so we should probably add them...

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

No branches or pull requests

2 participants