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

GraphQL schema merging reports "Invalid schema passed" #6760

Closed
lucatk opened this issue Jun 28, 2020 · 9 comments
Closed

GraphQL schema merging reports "Invalid schema passed" #6760

lucatk opened this issue Jun 28, 2020 · 9 comments

Comments

@lucatk
Copy link
Contributor

lucatk commented Jun 28, 2020

Issue Description

After reading #6360 I attempted to pass a custom graphql schema into Parse options, just like the example suggests. (see code below)
However, /graphql ceases to respond and outputs an error to the console.

I am pretty sure I have just missed some little detail, but after extensive research I can't find it. There's not much documentation around supplying a custom schema (except for the PR or supplying a .graphql file and using a Cloud Code-based resolver, which doesn't suffice for my use case, though), so I'd appreciate a slight nudge in the right direction.. :)

Steps to reproduce

  • Start custom parse-server using JS (I copied the approach from parse-server cli binary)
  • Pass custom graphql schema to options.graphQLSchema
options.graphQLSchema = new GraphQLSchema({
  query: new GraphQLObjectType({
    name: 'Query',
    fields: {
      // Here we define a custom Query field with the associated resolver
      customQuery: {
        type: new GraphQLNonNull(GraphQLString),
        args: {
          message: { type: new GraphQLNonNull(GraphQLString) },
        },
        resolve: (source, args, context, queryInfo) => {
          console.log('source', source);
          console.log('args', args);
          console.log('context', context);
          console.log('queryInfo', queryInfo);
          return args.message;
        },
      },
    },
  }),
});
  • Request /graphql endpoint

Expected Results

The request should resolve and graphql should be usable. My custom query should be usable.

Actual Outcome

/graphql does not respond. Errors in console. (see below)

Environment Setup

  • Server

    • parse-server version (Be specific! Don't say 'latest'.) : 4.2.0
    • Operating System: Docker, node-lts base image
    • Hardware: MacBook Pro 2019
    • Localhost or remote server? (AWS, Heroku, Azure, Digital Ocean, etc): localhost
  • Database

    • MongoDB version: 4.2.8
    • Storage engine: default
    • Hardware: MacBook Pro 2019
    • Localhost or remote server? (AWS, mLab, ObjectRocket, Digital Ocean, etc): localhost

Logs/Trace

error: Error: Invalid schema passed
    at /parse/node_modules/parse-server/node_modules/graphql-tools/dist/stitching/mergeSchemas.js:125:19
    at Array.forEach (<anonymous>)
    at mergeSchemasImplementation (/parse/node_modules/parse-server/node_modules/graphql-tools/dist/stitching/mergeSchemas.js:53:13)
    at mergeSchemas (/parse/node_modules/parse-server/node_modules/graphql-tools/dist/stitching/mergeSchemas.js:30:12)
    at ParseGraphQLSchema.load (/parse/node_modules/parse-server/lib/GraphQL/ParseGraphQLSchema.js:193:61)
    at processTicksAndRejections (internal/process/task_queues.js:97:5)
    at async ParseGraphQLServer._getGraphQLOptions (/parse/node_modules/parse-server/lib/GraphQL/ParseGraphQLServer.js:61:17)
    at async /parse/node_modules/parse-server/lib/GraphQL/ParseGraphQLServer.js:99:86
@lucatk
Copy link
Contributor Author

lucatk commented Jun 29, 2020

@Moumouls As the author of the PR, may you have a look at this? I have used your example from the PR.

@Moumouls
Copy link
Member

@lucatk thanks for report, your schema seems valid for me.
Since you get an Invalid Schema error, i think that the final schema that the parse graphql server receive is not the good one.
Can you provide your full ParseServer setup (options object + start) ?

@Moumouls
Copy link
Member

Note: I currently use this feature extensively on high security projects, with many custom type/query/mutation, it works well. So you probably have a configuration issue here 😃

@lucatk
Copy link
Contributor Author

lucatk commented Jun 29, 2020

Thanks for taking a look at it :)

As I said, I have copied most of the boilerplate from the cli/ directory, so here's my startup script:

const path = require('path');
const definitions = require('parse-server/lib/Options/Definitions').ParseServerOptions;

const ParseServer = require('parse-server').default;

const { GraphQLSchema, GraphQLObjectType, GraphQLNonNull, GraphQLString } = require('graphql');

function parseConfigFile() {
  let options = {};
  const jsonPath = path.resolve('/parse/config/config.json');
  const jsonConfig = require(jsonPath);
  if (jsonConfig.apps) {
    if (jsonConfig.apps.length > 1) {
      throw 'Multiple apps are not supported';
    }
    options = jsonConfig.apps[0];
  } else {
    options = jsonConfig;
  }
  Object.keys(options).forEach(key => {
    const value = options[key];
    if (!definitions[key]) {
      throw `error: unknown option ${key}`;
    }
    const action = definitions[key].action;
    if (action) {
      options[key] = action(value);
    }
  });
  console.log(`Configuration loaded from ${jsonPath}`);
  return options;
}
function getOptions(_opts) {
  return Object.keys(definitions).reduce((options, key) => {
    if (typeof _opts[key] !== 'undefined') {
      options[key] = _opts[key];
    }
    return options;
  }, {});
}
function logStartupOptions(options) {
  for (const key in options) {
    let value = options[key];
    if (key == 'masterKey') {
      value = '***REDACTED***';
    }
    if (key == 'push' && options.verbose != true) {
      value = '***REDACTED***';
    }
    if (typeof value === 'object') {
      try {
        value = JSON.stringify(value);
      } catch (e) {
        if (value && value.constructor && value.constructor.name) {
          value = value.constructor.name;
        }
      }
    }
    /* eslint-disable no-console */
    console.log(`${key}: ${value}`);
    /* eslint-enable no-console */
  }
}
function printSuccessMessage() {
  console.log(
    '[' + process.pid + '] Wherr parse-server running on ' + options.serverURL
  );
  if (options.mountGraphQL) {
    console.log(
      '[' +
        process.pid +
        '] GraphQL running on http://localhost:' +
        options.port +
        options.graphQLPath
    );
  }
  if (options.mountPlayground) {
    console.log(
      '[' +
        process.pid +
        '] Playground running on http://localhost:' +
        options.port +
        options.playgroundPath
    );
  }
}

const options = getOptions(parseConfigFile());
options.graphQLSchema = new GraphQLSchema({
  query: new GraphQLObjectType({
    name: 'Query',
    fields: {
      // Here we define a custom Query field with the associated resolver
      customQuery: {
        type: new GraphQLNonNull(GraphQLString),
        args: {
          message: { type: new GraphQLNonNull(GraphQLString) },
        },
        resolve: (source, args, context, queryInfo) => {
          console.log('source', source);
          console.log('args', args);
          console.log('context', context);
          console.log('queryInfo', queryInfo);
          return args.message;
        },
      },
    },
  }),
});

// TODO: cluster work (https://github.com/parse-community/parse-server/blob/67bf868208f8906793143a20eef4987b2cbffc9d/src/cli/parse-server.js#L69)

ParseServer.start(options, () => {
  logStartupOptions(options);
  console.log('');
  printSuccessMessage();
});

So my options object should just consist of the data I get from my config.json and the passed in custom schema.
This is the options object output as JSON:

{
  "appId": "***",
  "cloud": "/parse/cloud/index.js",
  "databaseURI": "mongodb://mongo/parse",
  "javascriptKey": "***",
  "masterKey": "***",
  "mountGraphQL": true,
  "restAPIKey": "***",
  "graphQLSchema": {
    "_queryType": "Query",
    "_directives": [
      "@include",
      "@skip",
      "@deprecated",
      "@specifiedBy"
    ],
    "_typeMap": {
      "Query": "Query",
      "String": "String",
      "Boolean": "Boolean",
      "__Schema": "__Schema",
      "__Type": "__Type",
      "__TypeKind": "__TypeKind",
      "__Field": "__Field",
      "__InputValue": "__InputValue",
      "__EnumValue": "__EnumValue",
      "__Directive": "__Directive",
      "__DirectiveLocation": "__DirectiveLocation"
    },
    "_subTypeMap": {},
    "_implementationsMap": {}
  },
  "allowClientClassCreation": true,
  "allowCustomObjectId": false,
  "cacheMaxSize": 10000,
  "cacheTTL": 5000,
  "collectionPrefix": "",
  "customPages": {},
  "directAccess": false,
  "enableAnonymousUsers": true,
  "enableExpressErrorHandler": false,
  "enableSingleSchemaCache": false,
  "expireInactiveSessions": true,
  "graphQLPath": "/graphql",
  "host": "0.0.0.0",
  "logsFolder": "./logs/",
  "masterKeyIps": [],
  "maxUploadSize": "20mb",
  "mountPath": "/parse",
  "mountPlayground": false,
  "objectIdSize": 10,
  "playgroundPath": "/playground",
  "port": 1337,
  "preserveFileName": false,
  "preventLoginWithUnverifiedEmail": false,
  "protectedFields": {
    "_User": {
      "*": [
        "email"
      ]
    }
  },
  "revokeSessionOnPasswordReset": true,
  "scheduledPush": false,
  "schemaCacheTTL": 5000,
  "sessionLength": 31536000,
  "verifyUserEmails": false,
  "jsonLogs": false,
  "verbose": false,
  "serverURL": "http://localhost:1337/parse"
}

@Moumouls
Copy link
Member

Additional question, what is your parse server version ?

@lucatk
Copy link
Contributor Author

lucatk commented Jun 29, 2020

It's 4.2.0, installed from NPM.

@lucatk
Copy link
Contributor Author

lucatk commented Jun 29, 2020

However, starting without the custom schema works out perfectly, and everything's normal.
So issue could also stem from somewhere around here?
I have also noticed there's quite a diff between the last release (4.2.0) and master. Could this be an issue with release 4.2.0, and on usage of master this might be resolved?

@lucatk
Copy link
Contributor Author

lucatk commented Jun 29, 2020

Seems like I resolved the issue!
Supplying a custom graphql schema via SDL using graphql package seems to require the same graphql package version as parse-server uses (which, in hindsight, makes sense...)!
I had used graphql@15.1.0, but instead needed to use @15.0.0.

@lucatk lucatk closed this as completed Jun 29, 2020
@Moumouls
Copy link
Member

@lucatk thanks for your investigation. Graphql package is hyper sensitive on versions (instances of GraphQLSchema are different from one version to another), and lead to merging issues.
We currently have a discussion here about this issue: #6732
If you have a suggestion or an idea, feel free to participate 😄

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