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

Allow Grats to validate context values #21

Closed
captbaritone opened this issue Nov 2, 2023 · 6 comments · Fixed by #29
Closed

Allow Grats to validate context values #21

captbaritone opened this issue Nov 2, 2023 · 6 comments · Fixed by #29
Labels
design Issue/concern about the design of the system enhancement New feature or request

Comments

@captbaritone
Copy link
Owner

captbaritone commented Nov 2, 2023

The third argument to a resolver is always the request context. This can be anything the user configures, so it's generally hard to typecheck effectively. However, Grats knows each function/method that is a GraphQL field (thanks to the @gqlField annotations) so could be charged with ensuring all fields are typed in a way that matches the developer's expectations.

This would require some way for Grats users to inform users what type they are passing to their executor. I'll need to brainstorm what options make sense here:

  1. Grats config file allows you to define a module path + export name
  2. Some well defined global type (GratsContextValue?) that can be defined by the user in their code and Grats will look for
  3. Some other TypeScript trick that I'm not thinking of?
  4. Edit /** @gqlContext */ on the context type declaration.

We should also examine how existing TS GraphQL servers address this. I'm thinking specifically of TypeGraphQL and Pothos.

@captbaritone captbaritone added enhancement New feature or request design Issue/concern about the design of the system labels Nov 2, 2023
@mandx
Copy link
Contributor

mandx commented Nov 2, 2023

Another option is to simply enforce that the context argument (if present) on all resolvers have to reference the exact same type. I doesn't matter what type it is (Request/string/{}/etc) but they all have to be the same.

So this would be valid:

function resolverOne(_: Foo, args: {}, context: MyContextType) {}
function resolverTwo(_: Bar, args: {}, context: MyContextType) {}

but this would make Grats emit an error:

function resolverOne(_: Foo, args: {}, context: MyContextType) {}
function resolverTwo(_: Bar, args: {}, context: string) {}

because MyContextType is not string.

Similarly, this would be fine:

function resolverOne(_: Foo, args: {}, context: string) {}
function resolverTwo(_: Bar, args: {}, context: string) {}

This as well:

function resolverOne(_: Foo, args: {}, context: MyContextType) {}
function resolverTwo(_: Bar, args: {}) {} // this doesn't use the `context` arg

And maybe, this could be allowed as well

function resolverOne(_: Foo, args: {}, context: MyContextType) {}
function resolverTwo(_: Bar, args: {}, context: unknown) {}

Typing it as unknown would be a safe-ish escape hatch, if one can't really refer to a concrete type there, forcing the resolver implementation to unsafely cast context to something else, or dynamically inspect the value with instanceof or type guards etc.

IMO this is an elegant way for Grats to enforce the type of context, doesn't require a config or docblock tags, etc.

It still on the developer to ensure the type of the context passed to the executor is the same the resolvers expect, I doubt Grats can help with that...

@captbaritone
Copy link
Owner Author

captbaritone commented Nov 2, 2023 via email

@captbaritone
Copy link
Owner Author

A related topic to context variable typing is the ability to support positional arguments. I've written an issue for that in #23. In short, if Grats can explicitly know which arg is typed as the context value (for example via a /** @gqlContext */ tag), it could allow you to define your arguments as positional arguments. See the #23 for more context.

@captbaritone
Copy link
Owner Author

I've tried out the @gqlContext approach (without positional arguments) here: #24

@mandx
Copy link
Contributor

mandx commented Nov 2, 2023

Kinda torn on positional arguments... I can see how it might be more ergonomic for others; OTOH, since GraphQL field arguments are keyword arguments, IMO it's nice that the args mirrors that. Also, keeping the resolvers' signature as close as possible to what graphql-js expects allows for easy migration to Grats, which is good for adoption.

Or maybe it's just that the args: {/* ... */} syntax is most familiar to me, and my brain is just resisting change 😅

@mandx
Copy link
Contributor

mandx commented Nov 2, 2023

I may pick a slightly more opinionated restriction: the ctx argument must be typed using a type that resolves to the same definition. That saves us from having to actually perform type checking (which is not fully supported by the TS library)

Btw I agree, it has to (nominally) resolve to the same type, so for example:

function resolverOne(_: Foo, args: {}, context: { foo: string; bar: number }) {}
function resolverTwo(_: Bar, args: {}, context: { foo: string; bar: number }) {}

It's my understanding that for TS, those two context args don't have the same type, but rather two independent types that happen to be 100% compatible between them. Proper type checking is needed to ensure that the types actually are compatible. Also, in practice, devs will name the type and then refer to that by name (possibly with the @gqlContext added as well) so IMO it's Ok to reject these cases and provide actions ("put the type under a name and use the name instead") as part of the error message.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
design Issue/concern about the design of the system enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants