graphql-tools icon indicating copy to clipboard operation
graphql-tools copied to clipboard

Stitching modifies errors after v8.6.9

Open electerious opened this issue 3 years ago • 3 comments

Issue workflow progress

Progress of the issue based on the Contributor Workflow

  • [ ] 1. The issue provides a reproduction available on Github, Stackblitz or CodeSandbox

    Make sure to fork this template and run yarn generate in the terminal.

    Please make sure the GraphQL Tools package versions under package.json matches yours.

  • [ ] 2. A failing test has been provided
  • [ ] 3. A local solution has been provided
  • [ ] 4. A pull request is pending review

Describe the bug

Something has changed in @graphql-tools/stitch that causes errors to be modified. Custom errors loose their fields and normal errors are getting new fields. I'm struggling to find the reason for it. I also couldn't find anything related in the changelog or code.

Here's how error.originalError looks with @graphql-tools/[email protected] when I log a custom error to the console:

ApiError: Server returned with status code 400
    at file:///Users/tobias.reich/Public/bff/packages/api/src/utils/send.js:85:9
  request: { ... },
  response: { ... }
}

Here's how error.originalError looks with @graphql-tools/[email protected] when I log a custom error to the console:

ApiError: Server returned with status code 400
    at file:///Users/tobias.reich/Public/bff/packages/api/src/utils/send.js:85:9 {
  path: [ '_createEntityTypeDefinition' ],
  locations: undefined,
  extensions: [Object: null prototype] {}
}

Gone are request and response. path, locations and extensions have been added. This is the same for normal errors, just that they don't loose their custom fields as they don't have any.

The errors have been logged in the formatError function of apollo-server. The error.originalError should contain the error that has been thrown (at least it did until I updated @graphql-tools/stitch).

Any idea why this is happening?

To Reproduce

  1. Stitch two schemas
import { stitchSchemas } from '@graphql-tools/stitch'

import {schema as a} from '../sources/a/index.js'
import {schema as b} from '../sources/b/index.js'

export default () => {
  return stitchSchemas({
    subschemas: [a,b],
    mergeTypes: false,
  })
}
  1. Throw an error in a resolver
Mutation: {
    _createEntityTypeDefinition: (parent, args, context) => {
        throw new Error('Test')
    }
} 
  1. Catch and log the original error in formatError

Expected behavior

The original error shouldn't be modified.

electerious avatar Jul 06 '22 09:07 electerious

I can confirm we see same behaviour. We are throwing custom errors in our resolvers and in formatError we are extracting some specific information from our errors and forwarding only those...

with 8.6.9 and up we are not getting our errors from resolvers anymore.

ghost avatar Sep 28 '22 09:09 ghost

Following our contribution flow, would any of you be interested in creating a failing test?

Urigo avatar Sep 28 '22 16:09 Urigo

Added failing test: https://github.com/ardatan/graphql-tools/pull/4739

ghost avatar Sep 29 '22 13:09 ghost

I found we can use error.originalError.originalError to get the actual original error.

Code

index.ts

import { makeExecutableSchema } from '@graphql-tools/schema';
import type { SubschemaConfig } from '@graphql-tools/delegate';
import { ApolloServer } from 'apollo-server';
import { stitchSchemas } from '@graphql-tools/stitch';

class CustomError extends Error {
  customField = 1;
}

const typeDefs = `
type Query {
  throwError: Boolean!
}
`;

const resolvers = {
  Query: {
    throwError() {
      throw new CustomError();
    },
  },
};

const subschema: SubschemaConfig = {
  schema: makeExecutableSchema({ typeDefs, resolvers }),
};

const { url } = await new ApolloServer({
  introspection: process.env.DEBUG === '1',
  schema: stitchSchemas({
    subschemas: [subschema],
  }),
  formatError(e) {
    console.log(0, e);
    console.log(1, e.originalError);
    console.log(2, (e.originalError as any)?.originalError);
    return e;
  },
}).listen({ port: 8080 });
console.log(`Listening at ${url}`);

Output:

0 [GraphQLError] {
  locations: undefined,
  path: [ 'throwError' ],
  extensions: {
    code: 'INTERNAL_SERVER_ERROR',
    exception: {
      message: '',
      path: [Array],
      locations: undefined,
      stacktrace: [Array]
    }
  }
}
1 Error
    at Object.throwError (file:///home/yuki/.local/src/github.com/acomagu/test-apollo-server/index.ts:19:13)
    at executeField (file:///home/yuki/.local/src/github.com/acomagu/test-apollo-server/node_modules/@graphql-tools/executor/esm/execution/execute.js:280:24)
    at executeFields (file:///home/yuki/.local/src/github.com/acomagu/test-apollo-server/node_modules/@graphql-tools/executor/esm/execution/execute.js:227:28)
    at executeOperation (file:///home/yuki/.local/src/github.com/acomagu/test-apollo-server/node_modules/@graphql-tools/executor/esm/execution/execute.js:191:18)
    at file:///home/yuki/.local/src/github.com/acomagu/test-apollo-server/node_modules/@graphql-tools/executor/esm/execution/execute.js:48:37
    at new ValueOrPromise (/home/yuki/.local/src/github.com/acomagu/test-apollo-server/node_modules/value-or-promise/src/ValueOrPromise.ts:35:15)
    at executeImpl (file:///home/yuki/.local/src/github.com/acomagu/test-apollo-server/node_modules/@graphql-tools/executor/esm/execution/execute.js:48:12)
    at execute (file:///home/yuki/.local/src/github.com/acomagu/test-apollo-server/node_modules/@graphql-tools/executor/esm/execution/execute.js:34:12)
    at ValueOrPromise.then.stopped (file:///home/yuki/.local/src/github.com/acomagu/test-apollo-server/node_modules/@graphql-tools/executor/esm/execution/normalizedExecutor.js:39:37)
    at new ValueOrPromise (/home/yuki/.local/src/github.com/acomagu/test-apollo-server/node_modules/value-or-promise/src/ValueOrPromise.ts:35:15) {
  path: [ 'throwError' ],
  locations: undefined,
  extensions: [Object: null prototype] {}
}
2 CustomError
    at Object.throwError (file:///home/yuki/.local/src/github.com/acomagu/test-apollo-server/index.ts:19:13)
    at executeField (file:///home/yuki/.local/src/github.com/acomagu/test-apollo-server/node_modules/@graphql-tools/executor/esm/execution/execute.js:280:24)
    at executeFields (file:///home/yuki/.local/src/github.com/acomagu/test-apollo-server/node_modules/@graphql-tools/executor/esm/execution/execute.js:227:28)
    at executeOperation (file:///home/yuki/.local/src/github.com/acomagu/test-apollo-server/node_modules/@graphql-tools/executor/esm/execution/execute.js:191:18)
    at file:///home/yuki/.local/src/github.com/acomagu/test-apollo-server/node_modules/@graphql-tools/executor/esm/execution/execute.js:48:37
    at new ValueOrPromise (/home/yuki/.local/src/github.com/acomagu/test-apollo-server/node_modules/value-or-promise/src/ValueOrPromise.ts:35:15)
    at executeImpl (file:///home/yuki/.local/src/github.com/acomagu/test-apollo-server/node_modules/@graphql-tools/executor/esm/execution/execute.js:48:12)
    at execute (file:///home/yuki/.local/src/github.com/acomagu/test-apollo-server/node_modules/@graphql-tools/executor/esm/execution/execute.js:34:12)
    at ValueOrPromise.then.stopped (file:///home/yuki/.local/src/github.com/acomagu/test-apollo-server/node_modules/@graphql-tools/executor/esm/execution/normalizedExecutor.js:39:37)
    at new ValueOrPromise (/home/yuki/.local/src/github.com/acomagu/test-apollo-server/node_modules/value-or-promise/src/ValueOrPromise.ts:35:15) {
  customField: 1
}

acomagu avatar Nov 04 '22 09:11 acomagu

could you provide a reproduction on CodeSandbox or StackBlitz?

ardatan avatar Nov 04 '22 10:11 ardatan

Closing the issue due to the missing reproduction. If you still have the issue, feel free to create a new one with a reproduction. Thanks!

ardatan avatar Mar 29 '23 06:03 ardatan