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

The context argument of inputRule has insufficient type definition #1315

Open
1 task done
Jinsung-L opened this issue Sep 17, 2021 · 3 comments
Open
1 task done

The context argument of inputRule has insufficient type definition #1315

Jinsung-L opened this issue Sep 17, 2021 · 3 comments

Comments

@Jinsung-L
Copy link

Bug report

  • I have checked other issues to make sure this is not a duplicate.

Describe the bug

inputRule is defined as follows:

export class InputRule<T> extends Rule {
constructor(
name: string,
schema: (yup: typeof Yup, ctx: IShieldContext) => Yup.Schema<T>,
options?: Yup.ValidateOptions,
) {
const validationFunction: IRuleFunction = (
parent: object,
args: object,
ctx: IShieldContext,
) =>
schema(Yup, ctx)
.validate(args, options)
.then(() => true)
.catch((err) => err)
super(name, validationFunction, { cache: 'strict', fragment: undefined })
}
}

Suppose that I have defined an input rule to check if there is already existing user with the given email address.

Then I would like I use the prisma client that I have passed in the context of the GraphQL resolver like as follows:

const isUniqueEmail = inputRule()((yup, context) =>
  yup.object({
    email: yup
      .string()
      .email()
      .test({
        name: 'unique-email',
        message: 'email already exists',
        test: (email) =>
          context.prisma.user.findUnique({ where: { email } }) === null,
      })
      .required(),
  })
)

However, since the type of context in IRuleFunction is defined as IShieldContext, type error occurs saying Property 'prisma' does not exist on type 'IShieldContext'

To Reproduce

Steps to reproduce the behavior, please provide code snippets or a repository:

(Delete the filler code and replace it with your own)

  1. This is my GraphQL Schema.
type Mutation {
  signup(email: String!): Boolean!
}
  1. This is the invoked query
mutation {
  signup(email: "[email protected]")
}
  1. I use these permissions
const isUniqueEmail = inputRule()((yup, context) =>
  yup.object({
    email: yup
      .string()
      .email()
      .test({
        name: 'unique-email',
        message: 'email already exists',
        test: (email) =>
          context.prisma.user.findUnique({ where: { email } }) === null,
      })
      .required(),
  })
)

const permissions = shield({
  Mutation: {
    signup: isUniqueEmail,
  },
})
  1. I use this context
const prisma = new PrismaClient()

const server = new ApolloServer({
  schema,
  context: { prisma },
})
  1. This is the error I see
Property 'prisma' does not exist on type 'IShieldContext'.

Expected behavior

Accessing prisma from the context argument should be able.

Actual behaviour

Can not access prisma from the context argument due to the type error.

Additional context

My current workaround is to use type casting.

interface Context {
  prisma: PrismaClient
}

const isUniqueEmail = inputRule()((yup, context) =>
  yup.object({
    email: yup
      .string()
      .email()
      .test({
        name: 'unique-email',
        message: 'email already exists',
        test: (email) =>
          (context as typeof context & Context).prisma.user.findUnique({ where: { email } }) === null,
      })
      .required(),
  })
)

But this workaround looks messy. So I think there should be essential solution to this problem.

I have two idea.

The first idea is

To remove the IShieldContext type definition for the context parameter and use any instead.

This way people may set their own types for the context parameter.

The downside of this is that it disables the type inference of IShieldContext. Not should whether it's useful though.

Normal rule defines its context parameter as any as well.

The second idea is

To use generic types.

Type definition of inputRule already uses one generic type T that I don't know what for.

You can use another generic type TContext to merge with IShieldContext.

export declare const inputRule: <TContext, T>(name?: string | undefined) => (schema: (yup: typeof Yup, ctx: TContext & IShieldContext) => Yup.Schema<T, object>, options?: Yup.ValidateOptions<object> | undefined) => InputRule<T>;

This way you can keep the IShieldContext while allowing users to define their own context type.

But I think you should either provide the default type for T or override the method or something like that. I'm not really familiar with using generic types, so please improve this idea to make it better.

@open-collective-bot
Copy link

Hey @Jinsung-L 👋,

Thank you for opening an issue. We will get back to you as soon as we can. Have you seen our Open Collective page? Please consider contributing financially to our project. This will help us involve more contributors and get to issues like yours faster.

https://opencollective.com/graphql-shield

We offer priority support for all financial contributors. Don't forget to add priority label once you become one! 😄

@Jinsung-L Jinsung-L changed the title The context argument of inputRule is insufficient type definition The context argument of inputRule has insufficient type definition Sep 24, 2021
@stale
Copy link

stale bot commented Apr 16, 2022

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Apr 16, 2022
@labelsync-manager labelsync-manager bot removed the stale label Apr 16, 2022
@wp99cp
Copy link

wp99cp commented May 23, 2023

Is there any update on this issue?

If you need a temporary solution, simply create a wrapper around the inputRule function to convert the type. I use this in my project and it works perfectly fine.

export const inputRuleWithContext = inputRule as unknown as <T>(
  name?: string
) => (
  schema: (
    yup: typeof Yup,
    ctx: GraphQLContext
  ) => Yup.BaseSchema<T, import('yup/lib/types.js').AnyObject, any>,
  options?:
    | import('yup/lib/types.js').ValidateOptions<import('yup/lib/types.js').AnyObject>
    | undefined
) => InputRule<T>;

Where GraphQLContext is the type of my graphQL context

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants