-
Notifications
You must be signed in to change notification settings - Fork 2k
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
extensions: add setParsed and capture error in extensions #2659
Conversation
591e0d6
to
bd51b22
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@evans this looks great! Thanks for finding this edge case with the operation registry (or other custom usages)
bd51b22
to
d937166
Compare
Currently this leaves out the operation name, which we should add to the |
Currently we don't set the parsed document in extensions immediately, which means that apollo-engine-reporting is forced to return a full signature instead of the normalized signature. This leads to cardinality explosion. This ensures that we report with a normalized signature and report errors that occur in the requestPipeline.
0fab957
to
4a2459e
Compare
Tested ^ on apigateway |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to avoid adding a very broad, dual-purpose setter method like this to the API surface area if we can avoid it, so I'd appreciate your thoughts on the following, which I think demonstrates how this could be done using existing information that's already available to the request pipeline.
(Also worth noting that didEncounterErrors
and the setting of documentAST
pre-didResolveOperation
are, I think, two separate and unrelated concerns?)
@@ -1,5 +1,7 @@ | |||
# Changelog | |||
|
|||
- extensions: add setParsed to ensure we get an operation signature and capture errors in extensions when they occur in unexpected locations [PR#2659](https://github.com/apollographql/apollo-server/pull/2659) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This CHANGELOG.md
entry doesn't match up with the method that was implemented (setParsed
vs. setParsedDocumentAndOperationName
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😬 that's an oversight. Good catch!
if (requestContext.document) { | ||
extensionStack.setParsedDocumentAndOperationName( | ||
requestContext.document, | ||
request.operationName, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The request.operationName
is already available to implementors of graphql-extensions
's requestDidStart
method via the operationName
parameter. The apollo-engine-reporting
extension just didn't take advantage of that, I think we can avoid implementing this new setParsedDocumentAndOperationName
method and instead allow the requestDidStart
within apollo-engine-reporting
to take care of that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yeah! It does. Love this
@@ -229,6 +234,10 @@ export async function processGraphQLRequest<TContext>( | |||
|
|||
try { | |||
requestContext.document = parse(query, config.parseOptions); | |||
extensionStack.setParsedDocumentAndOperationName( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than introducing a new public method to the extensions API, could we just access requestContext.document
within the requestDidEnd
callback of apollo-engine-reporting
if its own documentAST
isn't already set?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah we can do that. I had originally thought that it would be a better solution! However, I wasn't willing to modify the type of the EndHandler
returned from the requestDidStart
function, since it breaks the types. With typescript inheritance/overrides, the functions need to be typed in the subclass as well, so I'd worry about breaking current implementations.
export type EndHandler = (...errors: Array<Error>) => void; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need to necessarily change that type. How do you feel about just using working within the scope of where we declare the requestDidStart
's EndHandler
in apollo-engine-reporting/../extension.ts
?
i.e. somewhere here:
apollo-server/packages/apollo-engine-reporting/src/extension.ts
Lines 232 to 237 in 7fb5a29
this.trace.fullQueryCacheHit = !!o.requestContext.metrics | |
.responseCacheHit; | |
const operationName = this.operationName || ''; | |
let signature; | |
if (this.documentAST) { |
We could just add:
const documentAST = this.documentAST || o.requestContext.document;
And then use documentAST
rather than this.documentAST
below it.
@@ -372,6 +381,11 @@ export async function processGraphQLRequest<TContext>( | |||
} | |||
|
|||
return sendResponse(response); | |||
} catch (error) { | |||
// an error was thrown during the parse, validate, execute phases, so we |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment is not really correct since the parse
and execute
phases are entirely wrapped in try
and catch
calls and would never be reported to didEncounterErrors
through this hook. Do we want them to?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's right! I think the didEncounterErrors
was really meant as a didEncounterExecutionErrors
originally. The only usage is:
apollo-server/packages/apollo-server-core/src/requestPipeline.ts
Lines 336 to 338 in 4a2459e
if (result.errors) { | |
extensionStack.didEncounterErrors(result.errors); | |
} |
The implementation in this PR is certainly a hammer, so I'd be happy to scope down didEncounterErrors
to surrounding the calls out to non-extension user land, ie plugins, execution, and possibly context creation(would be nice to know about db connection failures)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can certainly see how all errors might be useful. However, if we keep this in place, we just need to also change some other things to account for it (i.e. the new rewriteError
#2618, which currently expects GraphQLError
s).
@@ -99,6 +103,17 @@ export class GraphQLExtensionStack<TContext = any> { | |||
ext => ext.parsingDidStart && ext.parsingDidStart(o), | |||
); | |||
} | |||
public setParsedDocumentAndOperationName( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think the name of this method name really needs to have parsed
in it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure! setOperationDocument
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe just setDocumentAndOperationName
if we need to keep it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the follow-up. I can tweak tomorrow, barring any final discussions.
@@ -229,6 +234,10 @@ export async function processGraphQLRequest<TContext>( | |||
|
|||
try { | |||
requestContext.document = parse(query, config.parseOptions); | |||
extensionStack.setParsedDocumentAndOperationName( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need to necessarily change that type. How do you feel about just using working within the scope of where we declare the requestDidStart
's EndHandler
in apollo-engine-reporting/../extension.ts
?
i.e. somewhere here:
apollo-server/packages/apollo-engine-reporting/src/extension.ts
Lines 232 to 237 in 7fb5a29
this.trace.fullQueryCacheHit = !!o.requestContext.metrics | |
.responseCacheHit; | |
const operationName = this.operationName || ''; | |
let signature; | |
if (this.documentAST) { |
We could just add:
const documentAST = this.documentAST || o.requestContext.document;
And then use documentAST
rather than this.documentAST
below it.
@@ -372,6 +381,11 @@ export async function processGraphQLRequest<TContext>( | |||
} | |||
|
|||
return sendResponse(response); | |||
} catch (error) { | |||
// an error was thrown during the parse, validate, execute phases, so we |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can certainly see how all errors might be useful. However, if we keep this in place, we just need to also change some other things to account for it (i.e. the new rewriteError
#2618, which currently expects GraphQLError
s).
@@ -99,6 +103,17 @@ export class GraphQLExtensionStack<TContext = any> { | |||
ext => ext.parsingDidStart && ext.parsingDidStart(o), | |||
); | |||
} | |||
public setParsedDocumentAndOperationName( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe just setDocumentAndOperationName
if we need to keep it?
This aims to accomplish one of the bits which was included within #2659 via a technique that scopes the errors more precisely. While I do think we should be passing all errors, the fix in #2659 was specifically aimed at a particular error-case which surfaced and we should consider a more refined approach to capturing those errors in the right places in future API iterations.
The work in #2659 was meant to ensure that the `document` and `operationName` were set at the appropriate locations within the lifecycle events for the `graphql-extensions` module. The change in #2659 introduced a new life-cycle method, but we could also calculate this information without adding to the API surface area using data which was already exposed to the extensions API. Even though `graphql-extensions` is no longer really being supported, avoiding growth on the API surface area of `graphql-extensions` is still important for any API. Ref: #2659
This aims to accomplish one of the bits which was included within #2659 via a technique that scopes the errors more precisely. While I do think we should be passing all errors, the fix in #2659 was specifically aimed at a particular error-case which surfaced and we should consider a more refined approach to capturing those errors in the right places in future API iterations.
The work in #2659 was meant to ensure that the `document` and `operationName` were set at the appropriate locations within the lifecycle events for the `graphql-extensions` module. The change in #2659 introduced a new life-cycle method, but we could also calculate this information without adding to the API surface area using data which was already exposed to the extensions API. Even though `graphql-extensions` is no longer really being supported, avoiding growth on the API surface area of `graphql-extensions` is still important for any API. Ref: #2659
The work in #2659 was meant to ensure that the `document` and `operationName` were set at the appropriate locations within the lifecycle events for the `graphql-extensions` module. The change in #2659 introduced a new life-cycle method, but we could also calculate this information without adding to the API surface area using data which was already exposed to the extensions API. Even though `graphql-extensions` is no longer really being supported, avoiding growth on the API surface area of `graphql-extensions` is still important for any API. Ref: #2659
This is another take on #2659 which has been otherwise excluded from the 2.6.0 release. The work in #2659 was meant to ensure that the `document` and `operationName` were set at the appropriate locations within the lifecycle events for the `graphql-extensions` module, specifically when an error had been thrown within `didResolveOperation` of the new request pipeline. While the solution in #2659 certainly works, it introduced a new life-cycle method for details which we could also calculate without adding to the API surface area by using information which was already exposed. Even though `graphql-extensions` is no longer really being supported, avoiding growth on the API surface area of `graphql-extensions` is still important for any API! Ref: #2659
Currently we don't set the parsed document in extensions immediately,
which means that apollo-engine-reporting is forced to return a full signature
instead of the normalized signature. This leads to cardinality
explosion. This ensures that we report with a normalized signature and
report errors that occur in the requestPipeline.
TODO: