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

Enables context creation to be async and capture errors with opt-in logging #1024

Merged
merged 21 commits into from
May 8, 2018

Conversation

evans
Copy link
Contributor

@evans evans commented May 3, 2018

This PR allows the context option in apollo-server-core's runQuery/runHttpQuery to be an async function, which would be common in authentication fetching scenarios.

Additionally, this PR increases the capture and formatting of errors during the creation of the options and context.

@ghost ghost added the ⛲️ feature New addition or enhancement to existing solutions label May 3, 2018
@evans evans force-pushed the evans/context-error branch from ba5ed13 to e06aa30 Compare May 3, 2018 19:51
@evans evans force-pushed the evans/context-error branch from e06aa30 to f1c1e79 Compare May 3, 2018 19:56
@evans evans force-pushed the evans/context-error branch from 412cc1c to fdd341c Compare May 4, 2018 00:50
throw new HttpQueryError(
500,
JSON.stringify({
errors: formatApolloErrors([e], { debug: debugDefault }),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error will return when the options to runHttpQuery are wrapped in a function, which throws an error. Currently this will only get called when the old middleware creation(expressGraphQL) is used, since the new server construction expects an object.

This line makes me nervous, since a production server that does not use a NODE_ENV of 'production' or 'test' will return a stacktrace to the client. A stacktrace is extremely informative for server creators to debug their systems. The other option would be to log the error in the console, which is non-ideal, since logging should be opt-in. The unfortunate part is that the user provided logging function is not resolved yet when the error occurs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appreciate you raising this concern here explicitly. Is there any way we can await the resolution of the logging function prior to throwing?

@evans evans requested review from abernix and jbaxleyiii May 4, 2018 17:46
@evans evans added this to the Release 2.0 milestone May 4, 2018
Copy link
Member

@abernix abernix left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking pretty good! Some comments within...

@@ -270,17 +278,16 @@ export class ApolloServerBase<Request = RequestInit> {
async request(request: Request) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The await itself has been removed, does this need to be an async function still?

error: GraphQLError,
debug: boolean = false,
) {
export function enrichError(error: GraphQLError, debug: boolean = false) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to be exported? Preference to not expose it if we don't need to!

@@ -270,17 +278,16 @@ export class ApolloServerBase<Request = RequestInit> {
async request(request: Request) {
let context: Context = this.context ? this.context : { request };

//Differ context resolution to inside of runQuery
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: Defer?

): Array<Error> {
const { formatter, debug, logFunction } = options;
return errors.map(error => enrichError(error, debug)).map(error => {
if (formatter !== undefined) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than having this larger if block surrounding the totality of this map, could we do something like:

  if (!formatter) {
    return error;
  }

  // Remainder of existing logic, indented by two less spaces.
  try {
     // ...

?

By hoisting that logic, it allows the reader of this code to focus on the remaining functionality, rather than needing to keep in mind that we're still inside a conditional block (the if).

@@ -218,9 +250,16 @@ export async function runHttpQuery(
return Promise.reject(e);
}

return Promise.resolve({ errors: [formatErrorFn(e)] });
return Promise.resolve({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Promise.resolve here can be removed since an async function implicitly wraps return values in Promise.resolve.

throw new HttpQueryError(
500,
JSON.stringify({
errors: formatApolloErrors([e], { debug: debugDefault }),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appreciate you raising this concern here explicitly. Is there any way we can await the resolution of the logging function prior to throwing?

Copy link
Member

@glasser glasser left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems basically fine although I don't actually know the motivation behind it. A few questions below.

@@ -4,6 +4,8 @@ All of the packages in the `apollo-server` repo are released with the same versi

### vNEXT

* Remove tests and guaranteed support for Node 4 [PR #1024](https://github.com/apollographql/apollo-server/pull/1024)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the motivation here? (I can certainly believe it's a pain to maintain Node 4 compat but I'm curious what the straw that broke the camel's back was.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://circleci.com/gh/apollographql/apollo-server/4564?utm_campaign=vcs-integration-link&utm_medium=referral&utm_source=github-build-link

The same code and setup lead to a different error code. Possibly worth investigating, though I think it's more distracting than valuable

@@ -2,5 +2,7 @@

### vNEXT

* `apollo-server-core`: Replace console.error with logFunction for opt-in logging [PR #1024](https://github.com/apollographql/apollo-server/pull/1024)
* `apollo-server-core`: errors in context creation can be async and is formatted to include error code [PR #1024](https://github.com/apollographql/apollo-server/pull/1024)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not just error can be async — context creation in general can be async?

@@ -267,20 +275,19 @@ export class ApolloServerBase<Request = RequestInit> {
if (this.engine || engineInRequestPath) this.engineEnabled = true;
}

async request(request: Request) {
request(request: Request) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just want to reiterate now that 2.0 is me and you that I find the name of this method to be incomprehensible and I hope we change it before release. Doesn't have to be in this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely agree! Will be another PR most likely

context = {};
} else if (typeof context === 'function') {
try {
context = await context();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So isn't this a backwards-incompatible change? Doesn't this break non-async context functions?

I mean that's OK because this is 2.0 I guess, but let's not bury the lede here...

Copy link
Contributor Author

@evans evans May 8, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

await unwraps a Promise if it's there. If the value on the rhs of await is an object or value, then it is returned immediately. I think this is bug fix or at minimum a strict addition to api to allow the context to return a Promise in addition to a value. What this change does break is context functions that returns a promise and the resolvers expect the context argument to be a Promise. I think this case is pretty unlikely and not a good best practice.

https://github.com/apollographql/apollo-server/pull/1024/files/4225c08b10c90dad5c0acc0b27fc72f10482ad12#diff-5102db46bcc7f6cc0c59149e9cc375d3R26 is the type addition to the context

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await
shows an example that has await 20.

@evans evans merged commit 33afa32 into refactor-2.0 May 8, 2018
@evans evans deleted the evans/context-error branch May 8, 2018 21:14
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 23, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
⛲️ feature New addition or enhancement to existing solutions
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants