npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@marcduez/graphql-validation-directives

v0.5.0

Published

<span class="badge-npmversion"><a href="https://npmjs.org/package/@marcduez/graphql-validation-directives" title="View this project on NPM"><img src="https://img.shields.io/npm/v/@marcduez/graphql-validation-directives.svg" alt="NPM version" /></a></span>

Downloads

7

Readme

graphql-validation-directives

Library for adding input validation to GraphQL services, using schema directives.

This library was heavily inspired by the approach used by @profusion/apollo-validation-directives.

Under the hood, this library:

  1. Uses the schema visitor logic to copy directive metadata into the extension methods of input objects, input object fields, and field arguments.
  2. Wraps the resolver functions of all fields with validated arguments, validating all arguments before calling the original resolver. If validation fails, a validation error is returned and the original resolver is never executed.

General usage

import { makeExecutableSchema } from "@graphql-tools/schema"
import {
  addValidationToSchema,
  ValidListDirective,
  ValidObjectDirective,
  ValidStringDirective,
} from "@marcduez/graphql-validation-directives"
import gql from "graphql-tag"

const validListDirective = new ValidListDirective()
const validObjectDirective = new ValidObjectDirective()
const validStringDirective = new ValidStringDirective()

const executableSchema = addValidationToSchema(
  validListDirective.applyDirectiveToSchema(
    validObjectDirective.applyDirectiveToSchema(
      validStringDirective.applyDirectiveToSchema(
        makeExecutableSchema({
          typeDefs: [
            validListDirective.typeDefs,
            validObjectDirective.typeDefs,
            validStringDirective.typeDefs,
            gql`
              input Mutation1Input
                @validObject(equalFields: ["string1", "string2"]) {
                list: [[String!]]
                  @validList(maxItems: 2, listDepth: 0)
                  @validList(uniqueItems: true, listDepth: 1)
                  @validString(startsWith: "abc")
                string1: String!
                string2: String!
              }

              type Mutation {
                mutation1(input: Mutation1Input!): Boolean!
                mutation2(
                  input: String! @validString(maxLength: 255, startsWith: "xyz")
                ): Boolean!
              }
            `,
          ],
          resolvers: [
            {
              Query: {
                mutation1: () => true,
                mutation2: () => true,
              },
            },
          ],
        })
      )
    )
  )
)

Null values

Null and undefined values are not validated. Use non-null GraphQL type hints for null checks.

List Depth

The GraphQL type system allows us to define list of lists. To target our validation at the right level of nesting, we need a way to specify the level of nesting. See documentation of BaseValidationDirective#getListDepth method for more details.

Custom directive names

If the default names of the validation directives collide with something in your own type definitions, you can use them with a custom name.

import { makeExecutableSchema } from "@graphql-tools/schema"
import {
  addValidationToSchema,
  ValidStringDirective,
} from "@marcduez/graphql-validation-directives"
import gql from "graphql-tag"

const validStringDirective = new ValidStringDirective("customDirectiveName")

const executableSchema = addValidationToSchema(
  validStringDirective.applyDirectiveToSchema(
    makeExecutableSchema({
      typeDefs: [
        validStringDirective.typeDefs,
        gql`
          type Mutation {
            mutation1(
              input: String!
                @customDirectiveName(maxLength: 255, startsWith: "xyz")
            ): Boolean!
          }
        `,
      ],
      resolvers: [
        {
          Query: {
            mutation1: () => true,
          },
        },
      ],
    })
  )
)

Built-in directives

@validString directive

format

Throws if a string does not match the provided format. Currently allowed values are EMAIL and UUID.

input Mutation1Input {
  field: String! @validString(format: EMAIL)
}

type Mutation {
  someMutation(arg: Mutation1Input!): Boolean!
}

OR

type Mutation {
  someMutation(arg: String! @validString(format: EMAIL)): Boolean!
}

maxLength

Throws if the string is longer than the provided value.

input Mutation1Input {
  field: String! @validString(maxLength: 255)
}

type Mutation {
  someMutation(arg: Mutation1Input!): Boolean!
}

OR

type Mutation {
  someMutation(arg: String! @validString(maxLength: 255)): Boolean!
}

minLength

Throws if the string is shorter than the provided value.

input Mutation1Input {
  field: String! @validString(minLength: 8)
}

type Mutation {
  someMutation(arg: Mutation1Input!): Boolean!
}

OR

type Mutation {
  someMutation(arg: String! @validString(minLength: 8)): Boolean!
}

startsWith

Throws if the string does not start with the provided value.

input Mutation1Input {
  field: String! @validString(startsWith: "account-")
}

type Mutation {
  someMutation(arg: Mutation1Input!): Boolean!
}

OR

type Mutation {
  someMutation(arg: String! @validString(startsWith: "account-")): Boolean!
}

endsWith

Throws if the string does not end with the provided value.

input Mutation1Input {
  field: String! @validString(endsWith: "-cad")
}

type Mutation {
  someMutation(arg: Mutation1Input!): Boolean!
}

OR

type Mutation {
  someMutation(arg: String! @validString(endsWith: "-cad")): Boolean!
}

includes

Throws if the string does not include the provided value.

input Mutation1Input {
  field: String! @validString(includes: "-rrsp-")
}

type Mutation {
  someMutation(arg: Mutation1Input!): Boolean!
}

OR

type Mutation {
  someMutation(arg: String! @validString(includes: "-rrsp-")): Boolean!
}

regex and regexFlags

Throws if the string does not match the provided regular expression pattern. If flags are provided, they are used.

input Mutation1Input {
  field: String! @validString(regex: "^[a-z0-9]$", regexFlags: "i")
}

type Mutation {
  someMutation(arg: Mutation1Input!): Boolean!
}

OR

type Mutation {
  someMutation(arg: String! @validString(regex: "^[a-z0-9]$", regexFlags: "i")): Boolean!
}

oneOf

Throws if the string is not in the provided collection of strings.

input Mutation1Input {
  field: String! @validString(oneOf: ["tfsa", "rrsp", "individual"])
}

type Mutation {
  someMutation(arg: Mutation1Input!): Boolean!
}

OR

type Mutation {
  someMutation(arg: String! @validString(oneOf: ["tfsa", "rrsp", "individual"])): Boolean!
}

@validInt directive

multipleOf

Throws if the number is not multiple of the provided integer value.

input Mutation1Input {
  field: Int! @validInt(multipleOf: 2)
}

type Mutation {
  someMutation(arg: Mutation1Input!): Boolean!
}

OR

type Mutation {
  someMutation(arg: Int! @validInt(multipleOf: 2)): Boolean!
}

max

Throws if the number is greater than the provided integer value.

input Mutation1Input {
  field: Int! @validInt(max: 2)
}

type Mutation {
  someMutation(arg: Mutation1Input!): Boolean!
}

OR

type Mutation {
  someMutation(arg: Int! @validInt(max: 2)): Boolean!
}

min

Throws if the number is less than the provided integer value.

input Mutation1Input {
  field: Int! @validInt(min: 2)
}

type Mutation {
  someMutation(arg: Mutation1Input!): Boolean!
}

OR

type Mutation {
  someMutation(arg: Int! @validInt(min: 2)): Boolean!
}

exclusiveMax

Throws if the number is greater than or equal to the provided integer value.

input Mutation1Input {
  field: Int! @validInt(exclusiveMax: 2)
}

type Mutation {
  someMutation(arg: Mutation1Input!): Boolean!
}

OR

type Mutation {
  someMutation(arg: Int! @validInt(exclusiveMax: 2)): Boolean!
}

exclusiveMin

Throws if the number is less than or equal to the provided integer value.

input Mutation1Input {
  field: Int! @validInt(exclusiveMin: 2)
}

type Mutation {
  someMutation(arg: Mutation1Input!): Boolean!
}

OR

type Mutation {
  someMutation(arg: Int! @validInt(exclusiveMin: 2.2)): Boolean!
}

oneOf

Throws if the number is not in the provided collection of integers.

input Mutation1Input {
  field: Int! @validInt(oneOf: [2, 3, 4])
}

type Mutation {
  someMutation(arg: Mutation1Input!): Boolean!
}

OR

type Mutation {
  someMutation(arg: Int! @validInt(oneOf: [2, 3, 4])): Boolean!
}

@validFloat directive

multipleOf

Throws if the number is not multiple of the provided float value.

input Mutation1Input {
  field: Float! @validFloat(multipleOf: 2.2)
}

type Mutation {
  someMutation(arg: Mutation1Input!): Boolean!
}

OR

type Mutation {
  someMutation(arg: Float! @validFloat(multipleOf: 2.2)): Boolean!
}

max

Throws if the number is greater than the provided float value.

input Mutation1Input {
  field: Float! @validFloat(max: 2.2)
}

type Mutation {
  someMutation(arg: Mutation1Input!): Boolean!
}

OR

type Mutation {
  someMutation(arg: Float! @validFloat(max: 2.2)): Boolean!
}

min

Throws if the number is less than the provided float value.

input Mutation1Input {
  field: Float! @validFloat(min: 2.2)
}

type Mutation {
  someMutation(arg: Mutation1Input!): Boolean!
}

OR

type Mutation {
  someMutation(arg: Float! @validFloat(min: 2.2)): Boolean!
}

exclusiveMax

Throws if the number is greater than or equal to the provided float value.

input Mutation1Input {
  field: Float! @validFloat(exclusiveMax: 2.2)
}

type Mutation {
  someMutation(arg: Mutation1Input!): Boolean!
}

OR

type Mutation {
  someMutation(arg: Float! @validFloat(exclusiveMax: 2.2)): Boolean!
}

exclusiveMin

Throws if the number is less than or equal to the provided float value.

input Mutation1Input {
  field: Float! @validFloat(exclusiveMin: 2.2)
}

type Mutation {
  someMutation(arg: Mutation1Input!): Boolean!
}

OR

type Mutation {
  someMutation(arg: Float! @validFloat(exclusiveMin: 2.2)): Boolean!
}

oneOf

Throws if the number is not in the provided collection of floats.

input Mutation1Input {
  field: Float! @validFloat(oneOf: [2.1, 2.2, 2.3])
}

type Mutation {
  someMutation(arg: Mutation1Input!): Boolean!
}

OR

type Mutation {
  someMutation(arg: Float! @validFloat(oneOf: [2.1, 2.2, 2.3])): Boolean!
}

@validList directive

maxItems

Throws if the list has more than the provided number of items.

input Mutation1Input {
  field: [String!]! @validList(maxItems: 5)
}

type Mutation {
  someMutation(arg: Mutation1Input!): Boolean!
}

OR

type Mutation {
  someMutation(arg: [String!]! @validList(maxItems: 5)): Boolean!
}

minItems

Throws if the list has less than the provided number of items.

input Mutation1Input {
  field: [String!]! @validList(minItems: 2)
}

type Mutation {
  someMutation(arg: Mutation1Input!): Boolean!
}

OR

type Mutation {
  someMutation(arg: [String!]! @validList(minItems: 2)): Boolean!
}

uniqueItems

Throws if the list has non-unique items.

input Mutation1Input {
  field: [String!]! @validList(uniqueItems: true)
}

type Mutation {
  someMutation(arg: Mutation1Input!): Boolean!
}

OR

type Mutation {
  someMutation(arg: [String!]! @validList(uniqueItems: true)): Boolean!
}

listDepth

See documentation of BaseValidationDirective#getListDepth method for more details on this feature.

input Mutation1Input {
  field: [[String!]!]! @validList(maxItems: 3) @validList(maxItems: 2, listDepth: 1)
}

type Mutation {
  someMutation(arg: Mutation1Input!): Boolean!
}

OR

type Mutation {
  someMutation(arg: [[String!]!]! @validList(maxItems: 3) @validList(maxItems: 2, listDepth: 1)): Boolean!
}

@validObject directive

equalFields

Throws if the fields with the provided names have non-equal values.

input Mutation1Input @validObject(equalFields: ["password", "confirmPassword"]) {
  password: String!
  confirmPassword: String!
}

type Mutation {
  someMutation(arg: Mutation1Input!): Boolean!
}

OR

input Mutation1Input {
  password: String!
  confirmPassword: String!
}

type Mutation {
  someMutation(arg: Mutation1Input! @validObject(equalFields: ["password", "confirmPassword"])): Boolean!
}

nonEqualFields

Throws if the fields with the provided names have equal values.

input Mutation1Input @validObject(nonEqualFields: ["securityAnswer1", "securityAnswer2"]) {
  securityAnswer1: String!
  securityAnswer2: String!
}

type Mutation {
  someMutation(arg: Mutation1Input!): Boolean!
}

OR

input Mutation1Input {
  securityAnswer1: String!
  securityAnswer2: String!
}

type Mutation {
  someMutation(arg: Mutation1Input! @validObject(nonEqualFields: ["securityAnswer1", "securityAnswer2"])): Boolean!
}

Writing custom directives

Below is an example of a custom validation directive that checks whether a string is a valid timezone name.

import { makeExecutableSchema } from "@graphql-tools/schema"
import {
  addValidationToSchema,
  BaseValidationDirective,
} from "@marcduez/graphql-validation-directives"
import gql from "graphql-tag"
import { getTimezone } from "countries-and-timezones"

class ValidTimezoneDirective extends BaseValidationDirective {
  constructor(name = "validTimezone") {
    super(name)
  }

  get typeDefs() {
    return gql(`
      directive @${this.name} on INPUT_FIELD_DEFINITION | ARGUMENT_DEFINITION
    `)
  }

  validate(_directiveConfig: Record<string, any>, value: any) {
    if (!getTimezone(value)) {
      throw new Error("Value must be a valid timezone")
    }
  }
}

const validTimezoneDirective = new ValidTimezoneDirective()

const executableSchema = addValidationToSchema(
  validTimezoneDirective.applyDirectiveToSchema(
    makeExecutableSchema({
      typeDefs: [
        validTimezoneDirective.typeDefs,
        gql`
          type Mutation {
            mutation1(input: String! @validTimezone): Boolean!
          }
        `,
      ],
      resolvers: [
        {
          Query: {
            mutation1: () => true,
          },
        },
      ],
    })
  )
)