strawberry icon indicating copy to clipboard operation
strawberry copied to clipboard

Improve operation directives and allow to skip/customise resolvers?

Open patrick91 opened this issue 2 years ago • 4 comments

So at the moment our operation directives only operate on results of fields, I think we might want to allow complete customisation of a field

So we can do this now:

@strawberry.directive(
    locations=[DirectiveLocation.FIELD], description="Make string uppercase"
)
def turn_uppercase(value: str):
    return value.upper()

but we could allow something like this:

@strawberry.directive(
    locations=[DirectiveLocation.FIELD], description="Make string uppercase"
)
def turn_uppercase(resolver: Callable[]):
    value = resolver()

    return value.upper()

what do you think @erikwrede @bellini666?

Upvote & Fund

  • We're using Polar.sh so you can upvote and help fund this issue.
  • We receive the funding once the issue is completed & confirmed by you.
  • Thank you in advance for helping prioritize & fund our backlog.
Fund with Polar

patrick91 avatar Nov 07 '23 15:11 patrick91

I thought of this while working on this PR: https://github.com/strawberry-graphql/strawberry/pull/3140

I think maybe one use case would be to update the context based on the directive on the operation

patrick91 avatar Nov 07 '23 15:11 patrick91

In my mental model, field extensions handle this. This is also why we once talked about allowing field extensions to apply schema directives to the respective fields. I'm not sure about the benefits of merging that into strawberry.directive. In any case, we need to define one clear path to solve such problems.

Either Field Directive -> Applies Field extension and adds directive to schema

Or Field Extension -> Can Specify a directive that is added when the extension is added

erikwrede avatar Nov 07 '23 18:11 erikwrede

@erikwrede this is for operation directives 😊

patrick91 avatar Nov 07 '23 18:11 patrick91

@patrick91 oops, the mentions of field wrongly made me think of field extensions 🤦 . As to this case, I believe we should allow also modifying the resolver & variables, i.e. pass the resolver to the directive function.

Speaking of field extensions, maybe we can re-use / unify some of that code as the flow of calling an operation extension would be very similar to that of a field extension.

Regarding the execution timing of directives, there's an important decision to be made:

Post-Individual Resolver Execution: Do we apply the directive immediately after each field's resolver has run? This would mean the directive operates on the output of a single field as soon as it's available. The advantage is that we can modify or utilize the output right away. For example, if a field resolver returns a string and we want to change it to uppercase, the directive can do so immediately after that specific resolver finishes its job.

Post-Subtree Resolution Execution: Or, should we wait until an entire section of the query (a "subtree") has been fully resolved before executing the directive? It allows the directive to consider the larger picture—the collective result of a type and all its nested fields—before making any changes.

The key question here is about timing: do we want to intervene early, field by field, or do we want to wait and have the complete context of a larger part of the graph before we act?

For field extensions / schema directives, the immediate answer for me is Post-Individual Resolver Execution. However, I am unsure wether this is the right way to go for Operation directives. As a user, I would expect such directives to be applied on the actual result I would normally receive. We can for now postpone this decision by just focusing on fields without a selection set, but this will be a discssion we need to have as we grow this feature 😊

erikwrede avatar Nov 07 '23 18:11 erikwrede