Skip to content

A collection of GraphQL custom scalars supporting cleanup, validation, and defaults via fluent api

Notifications You must be signed in to change notification settings

Hello10/graphql-validated-types

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

51 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

graphql-validated-types

A collection of GraphQL custom scalars supporting cleanup, validation, and defaults via fluent api.

Example

Hex colors

// Holds colors like `FF00FF` and `663399`
const HexColor = new GraphQLValidatedString({
  name: 'HexColor',
  description: 'HexColor string'
}).toUpperCase().hex().length(6).default('000000');

Example Project

There's an example of using this library with express in /example. You can run locally by doing

git clone https://github.com/stephenhandley/graphql-validated-types
cd graphql-validated-types
npm run example

Usage

The base class other types extend. It is an extension of GraphQLScalarType and can itself be instantiated as a custom scalar for use as a placeholder or to attach custom validators. Validators should either throw an error on invalid input, or return the the value (perhaps transformed) when it is valid. They can be chained as below and are run in the order they are added.

const VowelCountButNoLetterE = new GraphQLValidatedScalar({
  name: 'VowelCountButNoLetterE'
}).validator((value)=> {
  if (value.match(/e/)) {
    throw new Error('E is not allowed');
  }
  return value;
}).validator((value)=> {
  let vowels = ['a', 'e', 'i', 'o', 'u'];
  let count = 0;
  for (let i = 0; i < value.length; i++) {
    let letter = value[i];
    if (vowels.indexOf(letter) !== -1) {
      count++;
    }
  }
  return count;
});

let count = VowelCountButNoLetterE.parseValue('animals');
Assert.equal(count, 3);

Assert.throws(()=> {
  VowelCountButNoLetterE.parseValue('forever');
}, /E is not allowed/);

Validation

Validation functions will throw TypeError unless the value matches criteria

.length(length)

Requires string to be of specified length (if passed number) or min and/or max (if passed object)

const VariableLength = new GraphQLValidatedString({
  name: 'VariableLength'
}).length({min: 5, max: 10});

Assert.throws(()=> {
  VariableLength.parseValue('abcd');
}, /has invalid length/);

const FixedLength = new GraphQLValidatedString({
  name: 'FixedLength'
}).length(8);

Assert.throws(()=> {
  FixedLength.parseValue('abcde');
}, /has invalid length/);
.nonempty()

Alias for .length({min: 1})

const NotEmpty = new GraphQLValidatedString({
  name: 'NotEmpty'
}).nonempty();

Assert.throws(()=> {
  NotEmtpy.parseValue('');
}, /has invalid length/);
.regex(pattern)

Requires value to match pattern

const HumanName = new GraphQLValidatedString({
  name: 'HumanName'
}).regex(/([a-zA-Z]{3,30}\s*)+/);

Assert.throws(()=> {
  HumanName.parseValue('aa');
}, /does not match/);
.base64()

Alias for .regex(/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/)

const Image = new GraphQLValidatedString({
  name: 'Image'
}).base64();

Assert.throws(()=> {
  Image.parseValue('=====');
}, /does not match/);
.hex()

Alias for .regex(/^[a-f0-9]+$/i)

const Hex = new GraphQLValidatedString({
  name: 'Hex'
}).hex();

Assert.throws(()=> {
  Hex.parseValue('=====');
}, /does not match/);
.alphanumeric()

Alias for .regex(/^[a-zA-Z0-9]+$/)

const Username = new GraphQLValidatedString({
  name: 'Username'
}).alphanumeric();

Assert.throws(()=> {
  Username.parseValue('!!!!!');
}, /does not match/);
.existsIn(arr)

Requires value to exist in arr

const Domain = new GraphQLValidatedString({
  name: 'Domain'
}).existsIn(['foo.com', 'bar.com']);

Assert.throws(()=> {
  Domain.parseValue('baz.com');
}, /not present in array/);

Cleanup

.trim()

Remove spaces from either side of string

const Trimmed = new GraphQLValidatedString({
  name: 'Trimmed'
}).trim();

Assert.equal(Trimmed.parseValue(' abc '), 'abc');
.replace(pattern, replacement)

Replace pattern with replacement

const Replace = new GraphQLValidatedString({
  name: 'Replace'
}).replace(/b+/, 'b');

Assert.equal(Replace.parseValue('abbbc'), 'abc');
.squish()

Trim sides and replace repeated spaces with single space

const Squish = new GraphQLValidatedString({
  name: 'Squish'
}).squish();

Assert.equal(Squish.parseValue(' ab  c'), 'ab c');
.truncate(length)

Limit string to maximum length

const Truncate = new GraphQLValidatedString({
  name: 'Truncate'
}).truncate(5);

Assert.equal(Truncate.parseValue('abcdef'), 'abcde');
.toUpperCase()

Make string upper case

let Upper = new GraphQLValidatedString({
  name: 'Upper'
}).toUpperCase();

Assert.equal(Upper.parseValue('abcDEF'), 'ABCDEF');
.toLowerCase()

Make string lower case

let Lower = new GraphQLValidatedString({
  name: 'Lower'
}).toLowerCase();

Assert.equal(Upper.parseValue('ABCdef'), 'abcdef');

Extends GraphQLValidatedString and validates Email using email-regex.

// exact email address passes
const Email = new GraphQLValidatedEmail();

// any string containing email address passes
const ContainsEmail = new GraphQLValidatedEmail().exact(false);

Extends GraphQLValidatedString and validates URL using url-regex.

// exact URL with protocol passes
const URL = new GraphQLValidatedURL();

// any string containing URL with or without protocol passes
const ContainsURL = new GraphQLValidatedURL().exact(false).strict(false);

Extends GraphQLValidatedString and validates Phone Number using phone-regex

// exact phone number passes
const Phone = new GraphQLValidatedPhoneNumber();

// any string containing phone number passes
const ContainsPhone = new GraphQLValidatedPhoneNumber().exact(false);

Extends GraphQLValidatedString and validates IP Address using ip-regex.

// validates string containing IP Address (either IPV4 or IPV6)
let IPAddress = new GraphQLValidatedIPAddress().exact(false);

// validates string equal to IPV4 Address
let IPV4Address = new GraphQLValidatedIPAddress().v4();

// validates string equal to IPV6 Address
let IPV6Address = new GraphQLValidatedIPAddress().v6();

.min(minimum)

Require to be at least minimum

let Count = new GraphQLValidatedNumber({
  name: 'Count'
}).min(10);

Assert.throws(()=> {
  Count.parseValue(9);
}, /below minimum value/);

.max(maximum)

Require to be at most maximum

let Count = new GraphQLValidatedNumber({
  name: 'Count'
}).max(10);

Assert.throws(()=> {
  Count.parseValue(11);
}, /above maximum value/);

.range([minimum, maximum])

Require to be at least minimum and at most maximum

let Count = new GraphQLValidatedNumber({
  name: 'Count'
}).range([10, 20]);

Assert.throws(()=> {
  Count.parseValue(21);
}, /not within range/);

.below(limit)

Require to be less than limit

let Count = new GraphQLValidatedNumber({
  name: 'Count'
}).below(10);

Assert.throws(()=> {
  Count.parseValue(10);
}, /not below limit/);

.above(limit)

Require to be more than limit

let Count = new GraphQLValidatedNumber({
  name: 'Count'
}).above(10);

Assert.throws(()=> {
  Count.parseValue(10);
}, /not above limit/);

.between([low, high])

Require to be more than low and less than high

let Count = new GraphQLValidatedNumber({
  name: 'Count'
}).between([10, 20]);

Assert.throws(()=> {
  Count.parseValue(10);
}, /not between limits/);

.positive()

Require number to be greater than zero

let Count = new GraphQLValidatedNumber({
  name: 'Count'
}).positive();

Assert.throws(()=> {
  Count.parseValue(0);
}, /not positive/);

.negative()

Require number to be less than zero

let Count = new GraphQLValidatedNumber({
  name: 'Count'
}).negative();

Assert.throws(()=> {
  Count.parseValue(0);
}, /not negative/);

.nonnegative()

Require number to be zero or greater

let Count = new GraphQLValidatedNumber({
  name: 'Count'
}).nonnegative();

Assert.throws(()=> {
  Count.parseValue(-1);
}, /negative/);

Extends GraphQLValidatedNumber and requires number to be 32-bit (between -2147483648 and 2147483647) and truncates from floats to integers

const Integer = new GraphQLValidatedInteger({
  name: 'Integer'
});

Assert.equal(Integer.parseValue(10.5), 10);

Parses native JS Dates

Parses and formats dates using Moment.js

Prior to using, make sure Moment is set on the constructor

const Moment = require('moment');
const {GraphQLValidatedMoment} = require('graphql-validated-moment');
GraphQLValidatedMoment.Moment = Moment;

Parsing

By default, uses Moment's parsing logic to handle ISO 8601, RFC 2822 Date time, and fall back to new Date(<input>).

const Time = new GraphQLValidatedMoment({
  name: 'Time'
});
const now = new Date();
Assert.equal(Time.parseValue(now).valueOf(), now.getTime());

.inputFormat(format)

Specifies custom input format

const Time = new GraphQLValidatedMoment({
  name: 'Time'
}).inputFormat('YYYY-MM-DD HH:mm Z');
let formatted = Time.parseValue('2010-10-20 4:30 +0000').format();
Assert.equal(formatted, '2010-10-19T21:30:00-07:00');

.outputFormat(format)

Specifies custom output format for serialization

const year = '2013';
const time = `${year}-02-08`;
const Time = new GraphQLValidatedMoment({
  name: 'Time'
}).outputFormat('YYYY');
const output = Time.serialize(Time.parseValue(time));
Assert.equal(output, year);

Validators

.before()

Requires date to be before now

const tomorrow = Moment().add({day: 1});
const next_day = tomorrow.clone().add({day: 1});

const Time = new GraphQLValidatedMoment({
  name: 'Time'
}).before(tomorrow);

Assert.throws(()=> {
  Time.parseValue(next_day);
}, /not before/);
.beforeNow()

Requires date to be before now

const Time = new GraphQLValidatedMoment({
  name: 'Time'
}).beforeNow();

const tomorrow = Moment().add({day: 1});
Assert.throws(()=> {
  Time.parseValue(tomorrow);
}, /not before/);
.after()

Requires date to be before now

const tomorrow = Moment().add({day: 1});
const next_day = tomorrow.clone().add({day: 1});

const Time = new GraphQLValidatedMoment({
  name: 'Time'
}).after(next_day);

Assert.throws(()=> {
  Time.parseValue(tomorrow);
}, /not after/);
.afterNow()

Requires date to be before now

const Time = new GraphQLValidatedMoment({
  name: 'Time'
}).afterNow();

const yesterday = Moment().subtract({day: 1});
Assert.throws(()=> {
  Time.parseValue(yesterday);
}, /not after/);

Wrapper on MongoDB's ObjectID. Handles parsing 24 char hex string, 12 byte string, or existing ObjectID. Serializes using .toHexString

Prior to using, make sure ObjectID is set on the constructor

const MongoDB = require('mongodb');
const {GraphQLValidatedObjectID} = require('graphql-validated-moment');
GraphQLValidatedObjectID.ObjectID = MongoDB.ObjectID;

const ObjectID = new GraphQLValidatedObjectID();
const OBJECT_ID_HEX_STRING = '59b035f1485caa25a5505f2d';
const OBJECT_ID_12_BYTE = 'aaaaaaaaaaaa';
const OBJECT_ID_12_BYTE_AS_HEX = '616161616161616161616161';

let oid = ObjectID.parseValue(OBJECT_ID_HEX_STRING);
Assert.equal(oid.toHexString(), OBJECT_ID_HEX_STRING);

oid = new MongoDB.ObjectID();
Assert.equal(ObjectID.parseValue(oid), oid);

oid = ObjectID.parseValue(OBJECT_ID_12_BYTE);
Assert.equal(oid.toHexString(), OBJECT_ID_12_BYTE_AS_HEX);
Assert.equal(ObjectID.serialize(oid), OBJECT_ID_12_BYTE_AS_HEX);

Parses semver i.e. 1.0.4

TODO

  • support for array and object type validation?

Notes

This is based on aspects adapted from the following

About

A collection of GraphQL custom scalars supporting cleanup, validation, and defaults via fluent api

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 4

  •  
  •  
  •  
  •