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

Schema Validation problems in Schema Generated by getExecutableSchema #198

Open
shamin2021 opened this issue May 10, 2024 · 3 comments
Open
Labels
bug Something isn't working

Comments

@shamin2021
Copy link

shamin2021 commented May 10, 2024

Describe the bug
When I use orchestrator and merge two subgraphs with Apollo Federation Directives as given below ,

String inventorySchema = "type Product @key(fields: \"upc\") @extends {\n" +
                "    upc: String! @external\n" +
                "    weight: Int @external\n" +
                "    price: Int @external\n" +
                "    inStock: Boolean\n" +
                "    shippingEstimate: Int @requires(fields: \"price weight\")\n" +
                "}";
String productSchema = "type Query {\n" +
      "    topProducts(first: Int = 5): [Product]\n" +
      "    productB (upc : String!): Product\n" +
      "}\n" +
      "\n" +
      "type Product @key(fields: \"upc\") {\n" +
      "    upc: String!\n" +
      "    name: String\n" +
      "    price: Int\n" +
      "    weight: Int\n" +
      "}\n";
String reviewSchema = "type Review @key(fields: \"id\") {\n" +
      "    id: ID!\n" +
      "    body: String\n" +
      "    product: Product @resolver(field:\"productB\", arguments: [{name : \"upc\", value: \"UPC001\"}])\n" +
      "}\n" +
      "\n" +
      "type User @key(fields: \"id\") @extends {\n" +
      "    id: ID! @external\n" +
      "    username: String @external\n" +
      "    reviews: [Review]\n" +
      "}\n" +
      "\n" +
      "type Product @key(fields: \"upc\") @extends {\n" +
      "    upc: String! @external\n" +
      "    reviews: [Review]\n" +
      "}\n" +
      "\n" +
      "# ================================\n" +
      "# define this as built-in directive\n" +
      "directive @resolver(field: String!, arguments: [ResolverArgument!]) on FIELD_DEFINITION\n" +
      "\n" +
      "# define this as built-in type\n" +
      "input ResolverArgument {\n" +
      "    name : String!\n" +
      "    value : String!\n" +
      "}";
String userSchema = "type Query {\n" +
      "    me: User\n" +
      "}\n" +
      "\n" +
      "type User @key(fields: \"id\") {\n" +
      "    id: ID!\n" +
      "    name: String\n" +
      "    username: String\n" +
      "}";

// Creating providers for your GraphQL services
GenericProvider inventoryService = new GenericProvider("http://localhost:8084/graphql", httpClient,
      inventorySchema, "inventory");
GenericProvider productService = new GenericProvider("http://localhost:8081/graphql", httpClient,
      productSchema, "product");
GenericProvider reviewService = new GenericProvider("http://localhost:8083/graphql", httpClient,
      reviewSchema, "review");
GenericProvider accountService = new GenericProvider("http://localhost:8082/graphql", httpClient,
      userSchema, "user");


RuntimeGraph runtimeGraph = SchemaStitcher.newBuilder()
      .service(accountService)
      .service(productService)
      .service(inventoryService)
      .service(reviewService)
      .build()
      .stitchGraph();

The stitched Schema when output through

String printSchema = new SchemaPrinter().print(runtimeGraph.getExecutableSchema());
 System.out.println(printSchema);

produces the output

"Directs the executor to include this field or fragment only when the `if` argument is true"
directive @include(
    "Included when true."
    if: Boolean!
  ) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

"Directs the executor to skip this field or fragment when the `if`'argument is true."
directive @skip(
    "Skipped when true."
    if: Boolean!
  ) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

directive @provides(fields: String!) on FIELD_DEFINITION

"Exposes a URL that specifies the behaviour of this scalar."
directive @specifiedBy(
    "The URL that specifies the behaviour of this scalar."
    url: String!
  ) on SCALAR

directive @extends on OBJECT | INTERFACE

directive @deprecated(reason: String = "No longer supported") on FIELD_DEFINITION | ENUM_VALUE

directive @key(fields: _FieldSet) repeatable on OBJECT | INTERFACE

directive @requires(fields: _FieldSet!) on FIELD_DEFINITION

directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

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

directive @external on FIELD_DEFINITION

"[product]"
type Product {
  inStock: Boolean
  name: String
  price: Int
  reviews: [Review]
  shippingEstimate: Int
  upc: String!
  weight: Int
}

"[]"
type Query {
  _namespace: String
  me: User
  productB(upc: String!): Product
  topProducts(first: Int = 5): [Product]
}

"[review]"
type Review @key(fields : "id") {
  body: String
  id: ID!
  product: Product @resolver(arguments : [{name : "upc", value : "UPC001"}], field : "productB")
}

"[user]"
type User {
  id: ID!
  name: String
  reviews: [Review]
  username: String
}

"A selection set"
scalar _FieldSet

"[review]"
input ResolverArgument {
  name: String!
  value: String!
}

When this is parsed

SchemaParser parser = new SchemaParser();
        TypeDefinitionRegistry typeDefinitionRegistry = parser.parse(Paths.get("src/main/resources/supergraph.graphqls").toFile());
        RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring().build();
        GraphQLSchema federatedSchema = Federation.transform(typeDefinitionRegistry, runtimeWiring)
                .build();
        System.out.println(federatedSchema);

There are multiple issues that arise as below ,

SchemaProblem{errors=['product' [@50:3] tried to use an undeclared directive 'resolver']}

SchemaProblem{errors=['include' type [@28:1] tried to redefine existing directive 'include' type [@1:1], 'skip' type [@30:1] tried to redefine existing directive 'skip' type [@6:1]]}

When I remove the duplicate entries for @include and @Skip and add the
directive @resolver(field: String!, arguments: [ResolverArgument!]) on FIELD_DEFINITION

the validation is successful

Can you please let me know how this can be fixed.

I further want to know if this stitched graph can be considered and treated as a supergraph similar to the one in APOLLO Federation.

@shamin2021 shamin2021 added the bug Something isn't working label May 10, 2024
@ashpak-shaikh
Copy link
Contributor

RuntimeGraph is supposed to hold the executable schema for you. The expectation is you will use this part of the schema to execute the request and not reparse it. Why are you trying to parse the schema again after the runtime graph is constructed?

@shamin2021
Copy link
Author

shamin2021 commented May 13, 2024

Sorry for the confusion , I just need to confirm,

Question 1 :
Is the runtimeGraph.getExecutableSchema() complying with the schema parser or not.

SchemaParser parser = new SchemaParser();
TypeDefinitionRegistry typeDefinitionRegistry = parser.parse(<generated runtimeGraph.getExecutableSchema() >);

Question 2 :
How can I generate the supergraph Schema, when the subgraphs are provided using the orchestrator-library.

@ashpak-shaikh
Copy link
Contributor

ashpak-shaikh commented May 13, 2024

runtimeGraph.getExecutableSchema() is the supergraph schema. you are printing it correctly, but that doesn't print the directive definition by default, Look at the options it takes it to print the complete schema https://github.com/graphql-java/graphql-java/blob/master/src/main/java/graphql/schema/idl/SchemaPrinter.java

I am curious why do you want to parse it again and going back from executable schema to TypeDefintiionRegistry.

The standard path in graphql-java is TypeDefintionRegistry and then executable schema. Here you already have an executable schema.

You can create an instance of GraphQLOrchestrator and use the runtimegraph to execute queries on it directly

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants