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

graphql-constraint-directive-plus

v1.5.3

Published

Validate GraphQL resolver input fields via constraint directives

Downloads

10

Readme

GraphQL constraint directive ^+^

Allows using @constraint directive to validate GraphQL resolver input data.

Inspired by Constraints Directives RFC and OpenAPI

This library is an extension of graphql-constraint-directive

This library by default uses validator for string validation.

We include a test suite that covers using all constraint directives (See running tests), except for the List constraint validator (WIP).

If you add support for validating additional string formats (or other validations), please make sure to add tests to cover success/failure cases before making a PR.

Install

npm install graphql-constraint-directive-plus

Usage

const ConstraintDirective = require("graphql-constraint-directive-plus");
const express = require("express");
const bodyParser = require("body-parser");
const { graphqlExpress } = require("apollo-server-express");
const { makeExecutableSchema } = require("graphql-tools");
const typeDefs = `
  type Query {
    books: [Book]
  }
  type Book {
    title: String
  }
  type Mutation {
    createBook(input: BookInput): Book
  }
  input BookInput {
    title: String! @constraint(minLength: 5, format: "email")
  }`;
const schema = makeExecutableSchema({
  typeDefs,
  schemaDirectives: { constraint: ConstraintDirective }
});
const app = express();

app.use("/graphql", bodyParser.json(), graphqlExpress({ schema }));

Integrations

Syncing validation for:

  • entity models (on mutation save)
  • forms (on field change or submit)

You can use the constraints of this library with graphql-typeorm-validation to generate decorators for [class-validator] that can be used on any entity model, such as a TypeOrm entity

@Entity
class Person extends BaseEntity {
  @MinLength(2)
  @MaxLength(60)
  name: string;
}

Use this with a typeorm connection to build an entity class map, where each map entry is a model class with matching class-validator validation decorators applied.

The validation constraints are extracted via graphGenTypeorm from a GraphQL type definition

import { buildEntityClassMap } from "graphql-typeorm-validation";

const entityClassMap = buildEntityClassMap(connection);
const { Post } = entityClassMap;
// ...

First your createPost mutation resolver applies directive constraints on input object. It can then call createPostModel to create an in-memory Post model, validate it and then save it to the typeorm storage repository.

const createPostModel = async ({title, text}) {
  try {
    let postRepository = connection.getRepository("Post");
    let post = new Post();
    post.title = title;
    post.text = text;
    await validate(post, {
      // ... optional validation options
    });
    await postRepository.save(post);
    return post
  } catch (err) {
    handleError(`createPostModel failure: ${err.message}`, err)
  }
}

Mapping to general entity models

graphGenTypeorm uses graphSchemaToJson to first convert GraphQL type definitions (schema) to a Javascript object (JSON).

You could feed this schema object directly to the mapper.

This can be done using the decorate class API which lets you decorate any entity class with class-validator decorators.

Syncing validations with forms

You can further use the schema object to generate Yup form validations, using custom models in json-schema-to-yup.

Yup goes hand-in-glove with Formik, the amazing form builder. Ideally you would then also generate the form, mapping model/type fields to form fields...

We then need to use Yup addMethod to create additional Yup validators to match those available for the @constraint directive

const validator = require("validator");

Yup.addMethod(Yup.string, "isHexColor", function(args) {
  const { message } = args;
  return this.test("hex-color", message, function(value) {
    const { path, createError } = this;
    // [value] - value of the property being tested
    // [path]  - property name,
    // ...
    return validator.isHexColor(value) || createError({ path, message });
  });
});

With a little "trickery", you can sync your validations across:

  • models
  • mutation resolvers
  • forms

Would love to see your contributions to make this dream into reality. Almost there!

API

String

minLength

@constraint(minLength: 5) Restrict to a minimum length

maxLength

@constraint(maxLength: 5) Restrict to a maximum length

startsWith

@constraint(startsWith: "foo") Ensure value starts with foo

endsWith

@constraint(endsWith: "foo") Ensure value ends with foo

contains

@constraint(contains: "foo") Ensure value contains foo

notContains

@constraint(notContains: "foo") Ensure value does not contain foo

pattern

@constraint(pattern: "^[0-9a-zA-Z]*$") Ensure value matches regex, e.g. alphanumeric

format

@constraint(format: "email") Ensure value is in a particular format

Supported formats:

  • alpha-numeric
  • alpha
  • ascii
  • byte
  • credit-card
  • currency-amount
  • data-uri
  • date-time
  • date
  • domain-name
  • email
  • hash
  • hex-color
  • ipv4
  • ipv6
  • isbn
  • magnet-uri
  • mime-type
  • mobile-phone
  • mongo-id
  • postal-code
  • uri
  • uuid

Format validator options can be set as additional directive arguments:

@constraint(format: 'alpha-numeric', locale: 'en-US')

Format options available (with default values):

  • alphaLocale (string)
  • postalLocale (string)
  • phoneLocale (string)
  • hashAlgo (string)
  • domainName (object)
  • email (object)
  • currency (object)
{
  locale: "en-US", // used by alpha, alphanumeric, postalCode and mobilePhone
  hashAlgo: "md5",
  domainName: {
    require_tld: true,
    allow_underscores: false,
    allow_trailing_dot: false
  },
  email: {
    allow_display_name: false,
    require_display_name: false,
    allow_utf8_local_part: true,
    require_tld: true,
    allow_ip_domain: false,
    domain_specific_validation: false
  },
  currency: {
    symbol: "$",
    require_symbol: false,
    allow_space_after_symbol: false,
    symbol_after_digits: false,
    allow_negatives: true,
    parens_for_negatives: false,
    negative_sign_before_digits: false,
    negative_sign_after_digits: false,
    allow_negative_sign_placeholder: false,
    thousands_separator: ",",
    decimal_separator: ".",
    allow_decimal: true,
    require_decimal: false,
    digits_after_decimal: [2],
    allow_space_after_digits: false
  }
}

Int/Float

min

@constraint(min: 3) Ensure value is greater than or equal to

max

@constraint(max: 3) Ensure value is less than or equal to

exclusiveMin

@constraint(exclusiveMin: 3) Ensure value is greater than

exclusiveMax

@constraint(exclusiveMax: 3) Ensure value is less than

multipleOf

@constraint(multipleOf: 10) Ensure value is a multiple

ConstraintDirectiveError

Each validation error throws a ConstraintDirectiveError. Combined with a formatError function, this can be used to customise error messages.

{
  code: 'ERR_GRAPHQL_CONSTRAINT_VALIDATION',
  fieldName: 'theFieldName',
  context: [ { arg: 'argument name which failed', value: 'value of argument' } ]
}
const formatError = function(error) {
  if (
    error.originalError &&
    error.originalError.code === "ERR_GRAPHQL_CONSTRAINT_VALIDATION"
  ) {
    // return a custom object
  }

  return error;
};

app.use("/graphql", bodyParser.json(), graphqlExpress({ schema, formatError }));

Customization

By default, the constraint directive uses validator.js

You can pass your own validator in the GraphQL context object, conforming to this API:

  • isLength(value)
  • contains(value)
  • isAlphanumeric(value, locale)
  • isAlpha(value, locale)
  • isAscii(value)
  • isByte(value)
  • isCreditCard(value)
  • isCurrency(value)
  • isDataUri(value)
  • isDateTime(value)
  • isDate(value)
  • isDomainName(value)
  • isEmail(value)
  • isHash(value)
  • isHexColor(value)
  • isIPv6(value)
  • isIPv4(value)
  • isIsbn(value)
  • isMagnetUri(value)
  • isMimeType(value)
  • isMobilePhone(value, locale)
  • isMongoId(value)
  • `isPostalCode(value, countryCode)
  • isUri(value)
  • isUUID(value)

Note: All the above methods expect value to be a string.

The default validator is wrapped as follows:

const wrappedValidator = {
  // wrap your own validator using the same API
  isLength: validator.isLength,
  contains: validator.contains,

  isAlpha: validator.isAlpha,
  isAlphanumeric: validator.isAlphanumeric,
  isAscii: validator.isAscii,

  isByte: validator.isBase64,

  isCreditCard: validator.isCreditCard,
  isCurrency: validator.isCurrency,

  isDataUri: validator.isDataURI,
  isDateTime: validator.isRFC3339,
  isDate: validator.isISO8601,
  isDomainName: validator.isFQDN,

  isEmail: validator.isEmail,

  isHash: validator.isHash,
  isHexColor: validator.isHexColor,

  isIPv6: value => validator.isIP(value, 6),
  isIPv4: value => validator.isIP(value, 4),
  isIsbn: validator.isISBN,

  isMagnetUri: validator.isMagnetURI,
  isMobilePhone: validator.isMobilePhone,
  isMongoId: validator.isMongoId,
  isMimeType: validator.isMimeType,

  isPostalCode: validator.isPostalCode,

  isUri: validator.isURL,
  isUUID: validator.isUUID
};

Validation messages

You can set a validationError function map on the GraphQL context object to provide your own validator error handlers.

  • format(key, value)
  • `string(name, msg, args[])
  • `number(name, msg, args[])

The format validators will call: validationError.format('date', value)

The string and number validators will call the error handler like this:

validationError.string(name, `Must match ${args.pattern}`, [
  { arg: "pattern", value: args.pattern }
]);

Note that the third argument contains a list where each object has an arg entry that indicates the constraint that failed. You can use this as a key to lookup in your own validation error message map to return or output a localized error message as you see fit.

Reusing validators

You can re-use the core validators as follows:

import {
  string,
  number,
  list
} from "graphql-constraint-directive/scalars/validate";
import { validationError } from "graphql-constraint-directive/scalars/error";
import { validator } from "graphql-constraint-directive/validator";

const field = "name";
const args = { minLength: 4, maxLength: 40 };
value = "John Smith";

string.validate(name, args, value, { validator, validationError });

Complex types

See Complex types

Development

Tests

Run npm run test:nolint to run all tests without linting

Resources

License

See License