-
Notifications
You must be signed in to change notification settings - Fork 535
feature(dynamic-context): Execute function for context #394
Conversation
Thank you for your pull request and welcome to our community. We require contributors to sign our Contributor License Agreement, and we don't seem to have you on file. In order for us to review and merge your code, please sign up at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need the corporate CLA signed. If you have received this in error or have any questions, please contact us at [email protected]. Thanks! |
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Facebook open source project. Thanks! |
Provides the ability to do post-AST validation (e.g. security) as well as to build the context dynamically with a user object or other information. Prettify and test fix added test for context function support
da03990
to
8dc4bdf
Compare
prettify and added test for exception
@matthewerwin Cool PR! Previously I made very similar one but instead of context, I used rootValue, see #253. One problem that both proposals have in common is that we change symantic of values, you can't pass function as context to Second problem is that with such approach we need to add callbacks for everything: context, rootValue, documentAST, etc. I think we need more flexiable solution which allow you to inject any pre-execute and even post-execute code. That's why I submited #391 which allow you wrap standard Would be great to hear you opinion about #391? |
@matthewerwin To provide more context for discussion here is your example converted to use #391: router.use('/graphql', graphqlExpress((req,res)=> {
return {
schema,
execute: async (executeArgs) :Promise<ExecutionResult> => {
const jwt:JwtContext = (<any>req).jwt_context;
let user:ExtendedUser|null = null;
if( jwt.access_token ) {
user = await SecurityDb.AuthenticatedUser_Get(jwt.access_token, settings.server.jwt.expiresInSeconds);
}
else throw new NoAccessTokenError("Unauthorized");
//class which can do AST-level access checks and provide request helper methods
return execute({
...executeArgs,
context: new RequestContext( req, res, executeArgs.document, user );
});
}
};
})); |
@IvanGoncharov thanks for the commentary and making me aware of your changes! One thing that is unclear to me is how to invoke the default execute function. I looked at your PR -- so is the internal execute({...executeArgs}) calling the default GraphQL execute function? Seems you would need to pass that internal function into the executeFn wrapper. Providing an override option of the internal execute function is certainly powerful for mocking/faking aspect -- but this is already possible by passing in a mock set of resolvers rather than overriding the execute function. Even for the 3rd party API calls you mention in your PR it seems those would also be possible from the resolver vs as a "middleware" unless the calls need to be made for all resolvers and in that case why not just use the dynamic context here with post-AST parsing to make your calls and put the results in the context. That approach also avoids future function signature issues that may come up by exposing an override to the internal execute(). To directly address your two points above:
This PR doesn't pass a function to execute() -- it evaluates context prior to execution of the resolver.
If I understand your suggestion on a more flexible solution...you're relating it to a pre/post execution hook, which in a formal middleware sense is very different than letting someone overwrite the execute( ) function -- i.e. middleware is a flexible pipeline, not an override of internals. A true middleware implementation would allow modification of the context, AST, rootValue in a generic fashion that you mention -- however, that's not the current architecture in the library and for middleware extensibility requires a lot more of a design change than is present in either of our PR's. Let me know your thoughts! |
@matthewerwin I forget to mention that import { execute } from 'graphql-js'
This function is part of public API of
Sorry for confusing explanation. I meant different situation when a user wants's to pass some function as the context for his resolvers. Since both router.use('/graphql', graphqlExpress((req,res)=> {
return {
schema,
context: (sql) => db.execute(sql)
};
})); You can address this issue by making separate Note: I still think overriding execute is better since it allows both
It's extremely complicated to implement GraphQL proxy inside resolver functions. Inside resolvers, you only get arguments of a certain field and almost nothing else. It's impossible to construct query for 3rd-party server based on just the name of field and args, you need to know entire query like above fields and also subselection. If you also take into account abstract types like interfaces and unions you need to But I think technical details of implementing GraphQL proxy is unrelated to this discussion, let's focus on making
I agree that my use case is pretty unique and shouldn't be a strong argument for adding features into this lib. But you need exactly the same functionality for logging queries and their results or for measuring the performance of
I don't see a lot of difference here since |
@IvanGoncharov thank you for shedding light on the public API method and excellent references -- influences my thoughts substantially around this topic. Under that light my preference would be to see #391 merged...what's been the delay? |
@matthewerwin I think it should be reviewed by someone from Facebook preferably @leebyron. |
Fixed in #394 |
Provides the ability to do post-AST validation (e.g. security) as well as to build the context dynamically with a user object or other information.
Seems often all this work is expected in the resolvers and that adds a lot of extra code to every resolver to verify AST only has the fields permissible for the user. Root context doesn't have the AST information and there don't seem to be any other useful injection points between the inbound request and the resolver.
With this setup we elegantly handle errors can provide a rich security context and AST-management toolset to the resolvers from a centralized location.
e.g. consider as follows: