From 400101dd621ffb878585dfbe92221c50526d5497 Mon Sep 17 00:00:00 2001 From: Chris Cuilla Date: Tue, 16 Jan 2018 12:37:24 -0700 Subject: [PATCH 1/9] Added NegativeInt and NegativeFloat scalars --- CHANGELOG.md | 5 ++ README.md | 28 +++++-- src/NegativeFloat.js | 37 +++++++++ src/NegativeInt.js | 37 +++++++++ src/__tests__/NegativeFloat.test.js | 113 ++++++++++++++++++++++++++++ src/__tests__/NegativeInt.test.js | 113 ++++++++++++++++++++++++++++ src/index.js | 6 ++ 7 files changed, 333 insertions(+), 6 deletions(-) create mode 100644 src/NegativeFloat.js create mode 100644 src/NegativeInt.js create mode 100644 src/__tests__/NegativeFloat.test.js create mode 100644 src/__tests__/NegativeInt.test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 738541cae..2b3ed7508 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.2.0] - 2018-01-17 +### Added +- NegativeInt +- NegativeFloat + ## [0.1.0] - 2017-07-14 ### Added - Initial Release - released as [`@okgrow/graphql-scalars`](https://www.npmjs.com/package/@okgrow/graphql-scalars) on npm. diff --git a/README.md b/README.md index 221f151b3..d5e0350c3 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,11 @@ scalar DateTime scalar PositiveInt scalar NonNegativeInt +scalar NegativeInt + scalar PositiveFloat scalar NonNegativeFloat +scalar NegativeFloat scalar EmailAddress scalar URL @@ -31,9 +34,15 @@ In your resolver map, first import them: ```js import { DateTime, + PositiveInt, + NonNegativeInt, + NegativeInt, + PositiveFloat, NonNegativeFloat, + NegativeFloat, + EmailAddress, URL, } from '@okgrow/graphql-scalars'; @@ -47,8 +56,11 @@ const myResolverMap = { PositiveInt, NonNegativeInt, + NegativeInt, + PositiveFloat, NonNegativeFloat, + NegativeFloat, EmailAddress, URL, @@ -137,12 +149,18 @@ Integers that will have a value of 0 or more. Uses [`parseInt()`](https://develo ### PositiveInt Integers that will have a value greater than 0. Uses [`parseInt()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt). +### NegativeInt +Integers that will have a value less than 0. Uses [`parseInt()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt). + ### NonNegativeFloat Floats that will have a value of 0 or more. Uses [`parseFloat()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseFloat). ### PositiveFloat Floats that will have a value greater than 0. Uses [`parseFloat()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseFloat). +### NegativeFloat +Floats that will have a value less than 0. Uses [`parseFloat()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseFloat). + ### EmailAddress A field whose value conforms to the standard internet email address format as specified in [RFC822](https://www.w3.org/Protocols/rfc822/). @@ -157,15 +175,11 @@ We'd like to keep growing this package, within reason, to include the scalar typ required when defining GraphQL schemas. We welcome both suggestions and pull requests. A couple of ideas we're considering are: -- NegativeInt -- NegativeFloat - -These are easy to add, we just haven't run into cases for them yet. - - PhoneNumber - PostalCode +- BLOB -These both have challenges in terms of making them globally useful so they need a bit of thought. +These all have challenges in terms of making them globally useful so they need a bit of thought. For `PhoneNumber` we can probably just use the [E.164 specification](https://en.wikipedia.org/wiki/E.164) which is simply `+17895551234`. The very powerful @@ -176,6 +190,8 @@ parse user input and _get_ the E.164 format to pass _into_ a schema. Postal codes are [a bit more involved](https://en.wikipedia.org/wiki/List_of_postal_codes). But, again, it's probably just a really long regex. +BLOBs could be a base64-encoded object of some kind. + ## What's this all about? GraphQL is a wonderful new approach to application data and API layers that's gaining momentum. If you have not heard of it, start [here](http://graphql.org/learn/) and check out diff --git a/src/NegativeFloat.js b/src/NegativeFloat.js new file mode 100644 index 000000000..f21fc0b0a --- /dev/null +++ b/src/NegativeFloat.js @@ -0,0 +1,37 @@ +import { GraphQLScalarType } from 'graphql'; +import { GraphQLError } from 'graphql/error'; +import { Kind } from 'graphql/language'; + +function processValue(value) { + if (isNaN(value)) { + throw new TypeError(`Value is not a number: ${value}`); + } + + if (!(value < 0)) { + throw new TypeError(`Value is not a negative number: ${value}`); + } + + return parseFloat(value); +} + +export default new GraphQLScalarType({ + name: 'PositiveFloat', + + description: 'Floats that will have a value less than 0.', + + serialize(value) { + return processValue(value); + }, + + parseValue(value) { + return processValue(value); + }, + + parseLiteral(ast) { + if (ast.kind !== Kind.FLOAT) { + throw new GraphQLError(`Can only validate floating point numbers as negative floating point numbers but got a: ${ast.kind}`); // eslint-disable-line max-len + } + + return processValue(ast.value); + }, +}); diff --git a/src/NegativeInt.js b/src/NegativeInt.js new file mode 100644 index 000000000..47468fa78 --- /dev/null +++ b/src/NegativeInt.js @@ -0,0 +1,37 @@ +import { GraphQLScalarType } from 'graphql'; +import { GraphQLError } from 'graphql/error'; +import { Kind } from 'graphql/language'; + +function processValue(value) { + if (isNaN(value)) { + throw new TypeError(`Value is not a number: ${value}`); + } + + if (!(value < 0)) { + throw new TypeError(`Value is not a negative number: ${value}`); + } + + return parseInt(value, 10); +} + +export default new GraphQLScalarType({ + name: 'PositiveInt', + + description: 'Integers that will have a value less than 0.', + + serialize(value) { + return processValue(value); + }, + + parseValue(value) { + return processValue(value); + }, + + parseLiteral(ast) { + if (ast.kind !== Kind.INT) { + throw new GraphQLError(`Can only validate integers as negative integers but got a: ${ast.kind}`); // eslint-disable-line max-len + } + + return processValue(ast.value); + }, +}); diff --git a/src/__tests__/NegativeFloat.test.js b/src/__tests__/NegativeFloat.test.js new file mode 100644 index 000000000..26aa1511f --- /dev/null +++ b/src/__tests__/NegativeFloat.test.js @@ -0,0 +1,113 @@ +/* global describe, test, expect */ + +import { Kind } from 'graphql/language'; + +import { NegativeFloat } from '../'; + +describe('NegativeFloat', () => { + describe('valid', () => { + describe('as float', () => { + test('serialize', () => { + expect(NegativeFloat.serialize(-123.45)).toBe(-123.45); + }); + + test('parseValue', () => { + expect(NegativeFloat.parseValue(-123.45)).toBe(-123.45); + }); + + test('parseLiteral', () => { + expect(NegativeFloat.parseLiteral({ value: -123.45, kind: Kind.FLOAT })).toBe(-123.45); + }); + }); + + describe('as string', () => { + test('serialize', () => { + expect(NegativeFloat.serialize('-123.45')).toBe(-123.45); + }); + + test('parseValue', () => { + expect(NegativeFloat.parseValue('-123.45')).toBe(-123.45); + }); + + test('parseLiteral', () => { + expect(NegativeFloat.parseLiteral({ value: '-123.45', kind: Kind.FLOAT })).toBe(-123.45); + }); + }); + }); + + describe('invalid', () => { + describe('zero', () => { + describe('as float', () => { + test('serialize', () => { + expect(() => NegativeFloat.serialize(0.0)).toThrow(/Value is not a negative number/); + }); + + test('parseValue', () => { + expect(() => NegativeFloat.parseValue(0.0)).toThrow(/Value is not a negative number/); + }); + + test('parseLiteral', () => { + expect(() => NegativeFloat.parseLiteral({ value: 0.0, kind: Kind.FLOAT })).toThrow(/Value is not a negative number/); + }); + }); + + describe('as string', () => { + test('serialize', () => { + expect(() => NegativeFloat.serialize('0.0')).toThrow(/Value is not a negative number/); + }); + + test('parseValue', () => { + expect(() => NegativeFloat.parseValue('0.0')).toThrow(/Value is not a negative number/); + }); + + test('parseLiteral', () => { + expect(() => NegativeFloat.parseLiteral({ value: '0.0', kind: Kind.FLOAT })).toThrow(/Value is not a negative number/); + }); + }); + }); + + describe('less than zero', () => { + describe('as float', () => { + test('serialize', () => { + expect(() => NegativeFloat.serialize(1.0)).toThrow(/Value is not a negative number/); + }); + + test('parseValue', () => { + expect(() => NegativeFloat.parseValue(1.0)).toThrow(/Value is not a negative number/); + }); + + test('parseLiteral', () => { + expect(() => NegativeFloat.parseLiteral({ value: 1.0, kind: Kind.FLOAT })).toThrow(/Value is not a negative number/); + }); + }); + + describe('as string', () => { + test('serialize', () => { + expect(() => NegativeFloat.serialize('1.0')).toThrow(/Value is not a negative number/); + }); + + test('parseValue', () => { + expect(() => NegativeFloat.parseValue('1.0')).toThrow(/Value is not a negative number/); + }); + + test('parseLiteral', () => { + expect(() => NegativeFloat.parseLiteral({ value: '1.0', kind: Kind.FLOAT })).toThrow(/Value is not a negative number/); + }); + }); + }); + + describe('not a number', () => { + test('serialize', () => { + expect(() => NegativeFloat.serialize('not a number')).toThrow(/Value is not a number/); + }); + + test('parseValue', () => { + expect(() => NegativeFloat.parseValue('not a number')).toThrow(/Value is not a number/); + }); + + test('parseLiteral', () => { + expect(() => NegativeFloat.parseLiteral({ value: 'not a number', kind: Kind.STRING })).toThrow(/Can only validate floating point numbers as negative floating point numbers but got a/); + }); + }); + }); +}); diff --git a/src/__tests__/NegativeInt.test.js b/src/__tests__/NegativeInt.test.js new file mode 100644 index 000000000..7bbf3e3b5 --- /dev/null +++ b/src/__tests__/NegativeInt.test.js @@ -0,0 +1,113 @@ +/* global describe, test, expect */ + +import { Kind } from 'graphql/language'; + +import { NegativeInt } from '../'; + +describe('NegativeInt', () => { + describe('valid', () => { + describe('as int', () => { + test('serialize', () => { + expect(NegativeInt.serialize(-123)).toBe(-123); + }); + + test('parseValue', () => { + expect(NegativeInt.parseValue(-123)).toBe(-123); + }); + + test('parseLiteral', () => { + expect(NegativeInt.parseLiteral({ value: -123, kind: Kind.INT })).toBe(-123); + }); + }); + + describe('as string', () => { + test('serialize', () => { + expect(NegativeInt.serialize('-123')).toBe(-123); + }); + + test('parseValue', () => { + expect(NegativeInt.parseValue('-123')).toBe(-123); + }); + + test('parseLiteral', () => { + expect(NegativeInt.parseLiteral({ value: '-123', kind: Kind.INT })).toBe(-123); + }); + }); + }); + + describe('invalid', () => { + describe('zero', () => { + describe('as int', () => { + test('serialize', () => { + expect(() => NegativeInt.serialize(0)).toThrow(/Value is not a negative number/); + }); + + test('parseValue', () => { + expect(() => NegativeInt.parseValue(0)).toThrow(/Value is not a negative number/); + }); + + test('parseLiteral', () => { + expect(() => NegativeInt.parseLiteral({ value: 0, kind: Kind.INT })).toThrow(/Value is not a negative number/); + }); + }); + + describe('as string', () => { + test('serialize', () => { + expect(() => NegativeInt.serialize('0')).toThrow(/Value is not a negative number/); + }); + + test('parseValue', () => { + expect(() => NegativeInt.parseValue('0')).toThrow(/Value is not a negative number/); + }); + + test('parseLiteral', () => { + expect(() => NegativeInt.parseLiteral({ value: '0', kind: Kind.INT })).toThrow(/Value is not a negative number/); + }); + }); + }); + + describe('less than zero', () => { + describe('as int', () => { + test('serialize', () => { + expect(() => NegativeInt.serialize(1)).toThrow(/Value is not a negative number/); + }); + + test('parseValue', () => { + expect(() => NegativeInt.parseValue(1)).toThrow(/Value is not a negative number/); + }); + + test('parseLiteral', () => { + expect(() => NegativeInt.parseLiteral({ value: 1, kind: Kind.INT })).toThrow(/Value is not a negative number/); + }); + }); + + describe('as string', () => { + test('serialize', () => { + expect(() => NegativeInt.serialize('1')).toThrow(/Value is not a negative number/); + }); + + test('parseValue', () => { + expect(() => NegativeInt.parseValue('1')).toThrow(/Value is not a negative number/); + }); + + test('parseLiteral', () => { + expect(() => NegativeInt.parseLiteral({ value: '1', kind: Kind.INT })).toThrow(/Value is not a negative number/); + }); + }); + }); + + describe('not a number', () => { + test('serialize', () => { + expect(() => NegativeInt.serialize('not a number')).toThrow(/Value is not a number/); + }); + + test('parseValue', () => { + expect(() => NegativeInt.parseValue('not a number')).toThrow(/Value is not a number/); + }); + + test('parseLiteral', () => { + expect(() => NegativeInt.parseLiteral({ value: 'not a number', kind: Kind.STRING })).toThrow(/Can only validate integers as negative integers but got a/); + }); + }); + }); +}); diff --git a/src/index.js b/src/index.js index b58233525..a31aa3adf 100644 --- a/src/index.js +++ b/src/index.js @@ -1,8 +1,10 @@ import DateTime from './DateTime'; import PositiveInt from './PositiveInt'; import NonNegativeInt from './NonNegativeInt'; +import NegativeInt from './NegativeInt'; import PositiveFloat from './PositiveFloat'; import NonNegativeFloat from './NonNegativeFloat'; +import NegativeFloat from './NegativeFloat'; import EmailAddress from './EmailAddress'; import URL from './URL'; @@ -10,8 +12,10 @@ export { // named DateTime, PositiveInt, NonNegativeInt, + NegativeInt, PositiveFloat, NonNegativeFloat, + NegativeFloat, EmailAddress, URL, }; @@ -20,8 +24,10 @@ export default { // as part of a default object DateTime, PositiveInt, NonNegativeInt, + NegativeInt, PositiveFloat, NonNegativeFloat, + NegativeFloat, EmailAddress, URL, }; From a71877bd417c7bf1011a8c110fd196f6e4b0cff8 Mon Sep 17 00:00:00 2001 From: Chris Cuilla Date: Wed, 17 Jan 2018 16:13:21 -0700 Subject: [PATCH 2/9] DRY-up, extract processValue(); stricter processValue(); more tests --- src/NegativeFloat.js | 20 +++--- src/NegativeInt.js | 20 +++--- src/NonNegativeFloat.js | 18 ++---- src/NonNegativeInt.js | 18 ++---- src/PositiveFloat.js | 18 ++---- src/PositiveInt.js | 18 ++---- src/__tests__/NegativeFloat.test.js | 59 ++++++++++++++++++ src/__tests__/NegativeInt.test.js | 74 ++++++++++++++++++++++ src/__tests__/NonNegativeFloat.test.js | 71 +++++++++++++++++++-- src/__tests__/NonNegativeInt.test.js | 86 ++++++++++++++++++++++++-- src/__tests__/PositiveFloat.test.js | 59 ++++++++++++++++++ src/__tests__/PositiveInt.test.js | 74 ++++++++++++++++++++++ src/utilities.js | 73 ++++++++++++++++++++++ 13 files changed, 522 insertions(+), 86 deletions(-) create mode 100644 src/utilities.js diff --git a/src/NegativeFloat.js b/src/NegativeFloat.js index f21fc0b0a..d577ae699 100644 --- a/src/NegativeFloat.js +++ b/src/NegativeFloat.js @@ -2,29 +2,23 @@ import { GraphQLScalarType } from 'graphql'; import { GraphQLError } from 'graphql/error'; import { Kind } from 'graphql/language'; -function processValue(value) { - if (isNaN(value)) { - throw new TypeError(`Value is not a number: ${value}`); - } +import { processValue, VALUE_RANGES, VALUE_TYPES } from './utilities'; - if (!(value < 0)) { - throw new TypeError(`Value is not a negative number: ${value}`); - } - - return parseFloat(value); +function _processValue(value) { + return processValue(value, VALUE_RANGES.NEGATIVE, VALUE_TYPES.FLOAT); } export default new GraphQLScalarType({ - name: 'PositiveFloat', + name: 'NegativeFloat', description: 'Floats that will have a value less than 0.', serialize(value) { - return processValue(value); + return _processValue(value); }, parseValue(value) { - return processValue(value); + return _processValue(value); }, parseLiteral(ast) { @@ -32,6 +26,6 @@ export default new GraphQLScalarType({ throw new GraphQLError(`Can only validate floating point numbers as negative floating point numbers but got a: ${ast.kind}`); // eslint-disable-line max-len } - return processValue(ast.value); + return _processValue(ast.value); }, }); diff --git a/src/NegativeInt.js b/src/NegativeInt.js index 47468fa78..8aa3d6376 100644 --- a/src/NegativeInt.js +++ b/src/NegativeInt.js @@ -2,29 +2,23 @@ import { GraphQLScalarType } from 'graphql'; import { GraphQLError } from 'graphql/error'; import { Kind } from 'graphql/language'; -function processValue(value) { - if (isNaN(value)) { - throw new TypeError(`Value is not a number: ${value}`); - } +import { processValue, VALUE_RANGES, VALUE_TYPES } from './utilities'; - if (!(value < 0)) { - throw new TypeError(`Value is not a negative number: ${value}`); - } - - return parseInt(value, 10); +function _processValue(value) { + return processValue(value, VALUE_RANGES.NEGATIVE, VALUE_TYPES.INT); } export default new GraphQLScalarType({ - name: 'PositiveInt', + name: 'NegativeInt', description: 'Integers that will have a value less than 0.', serialize(value) { - return processValue(value); + return _processValue(value); }, parseValue(value) { - return processValue(value); + return _processValue(value); }, parseLiteral(ast) { @@ -32,6 +26,6 @@ export default new GraphQLScalarType({ throw new GraphQLError(`Can only validate integers as negative integers but got a: ${ast.kind}`); // eslint-disable-line max-len } - return processValue(ast.value); + return _processValue(ast.value); }, }); diff --git a/src/NonNegativeFloat.js b/src/NonNegativeFloat.js index 1a918e975..50fe7daa3 100644 --- a/src/NonNegativeFloat.js +++ b/src/NonNegativeFloat.js @@ -2,16 +2,10 @@ import { GraphQLScalarType } from 'graphql'; import { GraphQLError } from 'graphql/error'; import { Kind } from 'graphql/language'; -function processValue(value) { - if (isNaN(value)) { - throw new TypeError(`Value is not a number: ${value}`); - } +import { processValue, VALUE_RANGES, VALUE_TYPES } from './utilities'; - if (value < 0) { - throw new TypeError(`Value is a negative number: ${value}`); - } - - return parseFloat(value); +function _processValue(value) { + return processValue(value, VALUE_RANGES.NON_NEGATIVE, VALUE_TYPES.FLOAT); } export default new GraphQLScalarType({ @@ -20,11 +14,11 @@ export default new GraphQLScalarType({ description: 'Floats that will have a value of 0 or more.', serialize(value) { - return processValue(value); + return _processValue(value); }, parseValue(value) { - return processValue(value); + return _processValue(value); }, parseLiteral(ast) { @@ -32,6 +26,6 @@ export default new GraphQLScalarType({ throw new GraphQLError(`Can only validate floating point numbers as non-negative floating point numbers but got a: ${ast.kind}`); // eslint-disable-line max-len } - return processValue(ast.value); + return _processValue(ast.value); }, }); diff --git a/src/NonNegativeInt.js b/src/NonNegativeInt.js index 325f24684..5f6f1f836 100644 --- a/src/NonNegativeInt.js +++ b/src/NonNegativeInt.js @@ -2,16 +2,10 @@ import { GraphQLScalarType } from 'graphql'; import { GraphQLError } from 'graphql/error'; import { Kind } from 'graphql/language'; -function processValue(value) { - if (isNaN(value)) { - throw new TypeError(`Value is not a number: ${value}`); - } +import { processValue, VALUE_RANGES, VALUE_TYPES } from './utilities'; - if (value < 0) { - throw new TypeError(`Value is a negative number: ${value}`); - } - - return parseInt(value, 10); +function _processValue(value) { + return processValue(value, VALUE_RANGES.NON_NEGATIVE, VALUE_TYPES.INT); } export default new GraphQLScalarType({ @@ -20,11 +14,11 @@ export default new GraphQLScalarType({ description: 'Integers that will have a value of 0 or more.', serialize(value) { - return processValue(value); + return _processValue(value); }, parseValue(value) { - return processValue(value); + return _processValue(value); }, parseLiteral(ast) { @@ -32,6 +26,6 @@ export default new GraphQLScalarType({ throw new GraphQLError(`Can only validate integers as non-negative integers but got a: ${ast.kind}`); // eslint-disable-line max-len } - return processValue(ast.value); + return _processValue(ast.value); }, }); diff --git a/src/PositiveFloat.js b/src/PositiveFloat.js index dd2af5bb0..1283b1f1d 100644 --- a/src/PositiveFloat.js +++ b/src/PositiveFloat.js @@ -2,16 +2,10 @@ import { GraphQLScalarType } from 'graphql'; import { GraphQLError } from 'graphql/error'; import { Kind } from 'graphql/language'; -function processValue(value) { - if (isNaN(value)) { - throw new TypeError(`Value is not a number: ${value}`); - } +import { processValue, VALUE_RANGES, VALUE_TYPES } from './utilities'; - if (!(value > 0)) { - throw new TypeError(`Value is not a positive number: ${value}`); - } - - return parseFloat(value); +function _processValue(value) { + return processValue(value, VALUE_RANGES.POSITIVE, VALUE_TYPES.FLOAT); } export default new GraphQLScalarType({ @@ -20,11 +14,11 @@ export default new GraphQLScalarType({ description: 'Floats that will have a value greater than 0.', serialize(value) { - return processValue(value); + return _processValue(value); }, parseValue(value) { - return processValue(value); + return _processValue(value); }, parseLiteral(ast) { @@ -32,6 +26,6 @@ export default new GraphQLScalarType({ throw new GraphQLError(`Can only validate floating point numbers as positive floating point numbers but got a: ${ast.kind}`); // eslint-disable-line max-len } - return processValue(ast.value); + return _processValue(ast.value); }, }); diff --git a/src/PositiveInt.js b/src/PositiveInt.js index 4c1b97fcd..e471dae0d 100644 --- a/src/PositiveInt.js +++ b/src/PositiveInt.js @@ -2,16 +2,10 @@ import { GraphQLScalarType } from 'graphql'; import { GraphQLError } from 'graphql/error'; import { Kind } from 'graphql/language'; -function processValue(value) { - if (isNaN(value)) { - throw new TypeError(`Value is not a number: ${value}`); - } +import { processValue, VALUE_RANGES, VALUE_TYPES } from './utilities'; - if (!(value > 0)) { - throw new TypeError(`Value is not a positive number: ${value}`); - } - - return parseInt(value, 10); +function _processValue(value) { + return processValue(value, VALUE_RANGES.POSITIVE, VALUE_TYPES.INT); } export default new GraphQLScalarType({ @@ -20,11 +14,11 @@ export default new GraphQLScalarType({ description: 'Integers that will have a value greater than 0.', serialize(value) { - return processValue(value); + return _processValue(value); }, parseValue(value) { - return processValue(value); + return _processValue(value); }, parseLiteral(ast) { @@ -32,6 +26,6 @@ export default new GraphQLScalarType({ throw new GraphQLError(`Can only validate integers as positive integers but got a: ${ast.kind}`); // eslint-disable-line max-len } - return processValue(ast.value); + return _processValue(ast.value); }, }); diff --git a/src/__tests__/NegativeFloat.test.js b/src/__tests__/NegativeFloat.test.js index 26aa1511f..47085dca7 100644 --- a/src/__tests__/NegativeFloat.test.js +++ b/src/__tests__/NegativeFloat.test.js @@ -36,6 +36,36 @@ describe('NegativeFloat', () => { }); describe('invalid', () => { + describe('null', () => { + test('serialize', () => { + expect(() => NegativeFloat.serialize(null)).toThrow(/Value is not a number/); + }); + + // FIXME: Does nothing. No throw. Call doesn't even seem to get to the parseValue() function. + // test('parseValue', () => { + // expect(() => NegativeFloat.parseValue(null)).toThrow(/Value is not a number/); + // }); + + test('parseLiteral', () => { + expect(() => NegativeFloat.parseLiteral({ value: null, kind: Kind.FLOAT })).toThrow(/Value is not a number/); + }); + }); + + describe('undefined', () => { + test('serialize', () => { + expect(() => NegativeFloat.serialize(undefined)).toThrow(/Value is not a number/); + }); + + // FIXME: Does nothing. No throw. Call doesn't even seem to get to the parseValue() function. + // test('parseValue', () => { + // expect(() => NegativeFloat.parseValue(undefined)).toThrow(/Value is not a number/); + // }); + + test('parseLiteral', () => { + expect(() => NegativeFloat.parseLiteral({ value: undefined, kind: Kind.FLOAT })).toThrow(/Value is not a number/); + }); + }); + describe('zero', () => { describe('as float', () => { test('serialize', () => { @@ -96,6 +126,20 @@ describe('NegativeFloat', () => { }); }); + describe('infinity', () => { + test('serialize', () => { + expect(() => NegativeFloat.serialize(Number.NEGATIVE_INFINITY)).toThrow(/Value is not a finite number/); + }); + + test('parseValue', () => { + expect(() => NegativeFloat.parseValue(Number.NEGATIVE_INFINITY)).toThrow(/Value is not a finite number/); + }); + + test('parseLiteral', () => { + expect(() => NegativeFloat.parseLiteral({ value: Number.NEGATIVE_INFINITY, kind: Kind.FLOAT })).toThrow(/Value is not a finite number/); + }); + }); + describe('not a number', () => { test('serialize', () => { expect(() => NegativeFloat.serialize('not a number')).toThrow(/Value is not a number/); @@ -109,5 +153,20 @@ describe('NegativeFloat', () => { expect(() => NegativeFloat.parseLiteral({ value: 'not a number', kind: Kind.STRING })).toThrow(/Can only validate floating point numbers as negative floating point numbers but got a/); }); }); + + describe('NaN', () => { + test('serialize', () => { + expect(() => NegativeFloat.serialize(Number.NaN)).toThrow(/Value is not a number/); + }); + + // FIXME: Does nothing. No throw. Call doesn't even seem to get to the parseValue() function. + // test('parseValue', () => { + // expect(() => NegativeFloat.parseValue(Number.NaN)).toThrow(/Value is not a number/); + // }); + + test('parseLiteral', () => { + expect(() => NegativeFloat.parseLiteral({ value: Number.NaN, kind: Kind.STRING })).toThrow(/Can only validate floating point numbers as negative floating point numbers but got a/); + }); + }); }); }); diff --git a/src/__tests__/NegativeInt.test.js b/src/__tests__/NegativeInt.test.js index 7bbf3e3b5..163779d9d 100644 --- a/src/__tests__/NegativeInt.test.js +++ b/src/__tests__/NegativeInt.test.js @@ -36,6 +36,51 @@ describe('NegativeInt', () => { }); describe('invalid', () => { + describe('null', () => { + test('serialize', () => { + expect(() => NegativeInt.serialize(null)).toThrow(/Value is not a number/); + }); + + // FIXME: Does nothing. No throw. Call doesn't even seem to get to the parseValue() function. + // test('parseValue', () => { + // expect(() => NegativeInt.parseValue(null)).toThrow(/Value is not a number/); + // }); + + test('parseLiteral', () => { + expect(() => NegativeInt.parseLiteral({ value: null, kind: Kind.INT })).toThrow(/Value is not a number/); + }); + }); + + describe('undefined', () => { + test('serialize', () => { + expect(() => NegativeInt.serialize(undefined)).toThrow(/Value is not a number/); + }); + + // FIXME: Does nothing. No throw. Call doesn't even seem to get to the parseValue() function. + // test('parseValue', () => { + // expect(() => NegativeInt.parseValue(undefined)).toThrow(/Value is not a number/); + // }); + + test('parseLiteral', () => { + expect(() => NegativeInt.parseLiteral({ value: undefined, kind: Kind.INT })).toThrow(/Value is not a number/); + }); + }); + + describe('unsafe integer', () => { + test('serialize', () => { + expect(() => NegativeInt.serialize(2 ** 53)).toThrow(/Value is not a safe integer/); + }); + + // FIXME: Does nothing. No throw. Call doesn't even seem to get to the parseValue() function. + test('parseValue', () => { + expect(() => NegativeInt.parseValue(2 ** 53)).toThrow(/Value is not a safe integer/); + }); + + test('parseLiteral', () => { + expect(() => NegativeInt.parseLiteral({ value: 2 ** 53, kind: Kind.INT })).toThrow(/Value is not a safe integer/); + }); + }); + describe('zero', () => { describe('as int', () => { test('serialize', () => { @@ -96,6 +141,20 @@ describe('NegativeInt', () => { }); }); + describe('infinity', () => { + test('serialize', () => { + expect(() => NegativeInt.serialize(Number.NEGATIVE_INFINITY)).toThrow(/Value is not a finite number/); + }); + + test('parseValue', () => { + expect(() => NegativeInt.parseValue(Number.NEGATIVE_INFINITY)).toThrow(/Value is not a finite number/); + }); + + test('parseLiteral', () => { + expect(() => NegativeInt.parseLiteral({ value: Number.NEGATIVE_INFINITY, kind: Kind.INT })).toThrow(/Value is not a finite number/); + }); + }); + describe('not a number', () => { test('serialize', () => { expect(() => NegativeInt.serialize('not a number')).toThrow(/Value is not a number/); @@ -109,5 +168,20 @@ describe('NegativeInt', () => { expect(() => NegativeInt.parseLiteral({ value: 'not a number', kind: Kind.STRING })).toThrow(/Can only validate integers as negative integers but got a/); }); }); + + describe('NaN', () => { + test('serialize', () => { + expect(() => NegativeInt.serialize(Number.NaN)).toThrow(/Value is not a number/); + }); + + // FIXME: Does nothing. No throw. Call doesn't even seem to get to the parseValue() function. + // test('parseValue', () => { + // expect(() => NegativeInt.parseValue(Number.NaN)).toThrow(/Value is not a number/); + // }); + + test('parseLiteral', () => { + expect(() => NegativeInt.parseLiteral({ value: Number.NaN, kind: Kind.STRING })).toThrow(/Can only validate integers as negative integers but got a/); + }); + }); }); }); diff --git a/src/__tests__/NonNegativeFloat.test.js b/src/__tests__/NonNegativeFloat.test.js index abfbec216..fb439e4d2 100644 --- a/src/__tests__/NonNegativeFloat.test.js +++ b/src/__tests__/NonNegativeFloat.test.js @@ -68,36 +68,80 @@ describe('NonNegativeFloat', () => { }); describe('invalid', () => { + describe('null', () => { + test('serialize', () => { + expect(() => NonNegativeFloat.serialize(null)).toThrow(/Value is not a number/); + }); + + // FIXME: Does nothing. No throw. Call doesn't even seem to get to the parseValue() function. + // test('parseValue', () => { + // expect(() => NonNegativeFloat.parseValue(null)).toThrow(/Value is not a number/); + // }); + + test('parseLiteral', () => { + expect(() => NonNegativeFloat.parseLiteral({ value: null, kind: Kind.FLOAT })).toThrow(/Value is not a number/); + }); + }); + + describe('undefined', () => { + test('serialize', () => { + expect(() => NonNegativeFloat.serialize(undefined)).toThrow(/Value is not a number/); + }); + + // FIXME: Does nothing. No throw. Call doesn't even seem to get to the parseValue() function. + // test('parseValue', () => { + // expect(() => NonNegativeFloat.parseValue(undefined)).toThrow(/Value is not a number/); + // }); + + test('parseLiteral', () => { + expect(() => NonNegativeFloat.parseLiteral({ value: undefined, kind: Kind.FLOAT })).toThrow(/Value is not a number/); + }); + }); + describe('less than zero', () => { describe('as float', () => { test('serialize', () => { - expect(() => NonNegativeFloat.serialize(-1.0)).toThrow(/Value is a negative number/); + expect(() => NonNegativeFloat.serialize(-1.0)).toThrow(/Value is not a non-negative number/); }); test('parseValue', () => { - expect(() => NonNegativeFloat.parseValue(-1.0)).toThrow(/Value is a negative number/); + expect(() => NonNegativeFloat.parseValue(-1.0)).toThrow(/Value is not a non-negative number/); }); test('parseLiteral', () => { - expect(() => NonNegativeFloat.parseLiteral({ value: -1.0, kind: Kind.FLOAT })).toThrow(/Value is a negative number/); + expect(() => NonNegativeFloat.parseLiteral({ value: -1.0, kind: Kind.FLOAT })).toThrow(/Value is not a non-negative number/); }); }); describe('as string', () => { test('serialize', () => { - expect(() => NonNegativeFloat.serialize('-1.0')).toThrow(/Value is a negative number/); + expect(() => NonNegativeFloat.serialize('-1.0')).toThrow(/Value is not a non-negative number/); }); test('parseValue', () => { - expect(() => NonNegativeFloat.parseValue('-1.0')).toThrow(/Value is a negative number/); + expect(() => NonNegativeFloat.parseValue('-1.0')).toThrow(/Value is not a non-negative number/); }); test('parseLiteral', () => { - expect(() => NonNegativeFloat.parseLiteral({ value: '-1.0', kind: Kind.FLOAT })).toThrow(/Value is a negative number/); + expect(() => NonNegativeFloat.parseLiteral({ value: '-1.0', kind: Kind.FLOAT })).toThrow(/Value is not a non-negative number/); }); }); }); + describe('infinity', () => { + test('serialize', () => { + expect(() => NonNegativeFloat.serialize(Number.POSITIVE_INFINITY)).toThrow(/Value is not a finite number/); + }); + + test('parseValue', () => { + expect(() => NonNegativeFloat.parseValue(Number.POSITIVE_INFINITY)).toThrow(/Value is not a finite number/); + }); + + test('parseLiteral', () => { + expect(() => NonNegativeFloat.parseLiteral({ value: Number.POSITIVE_INFINITY, kind: Kind.FLOAT })).toThrow(/Value is not a finite number/); + }); + }); + describe('not a number', () => { test('serialize', () => { expect(() => NonNegativeFloat.serialize('not a number')).toThrow(/Value is not a number/); @@ -111,5 +155,20 @@ describe('NonNegativeFloat', () => { expect(() => NonNegativeFloat.parseLiteral({ value: 'not a number', kind: Kind.STRING })).toThrow(/Can only validate floating point numbers as non-negative floating point numbers but got a/); }); }); + + describe('NaN', () => { + test('serialize', () => { + expect(() => NonNegativeFloat.serialize(Number.NaN)).toThrow(/Value is not a number/); + }); + + // FIXME: Does nothing. No throw. Call doesn't even seem to get to the parseValue() function. + // test('parseValue', () => { + // expect(() => NonNegativeFloat.parseValue(Number.NaN)).toThrow(/Value is not a number/); + // }); + + test('parseLiteral', () => { + expect(() => NonNegativeFloat.parseLiteral({ value: Number.NaN, kind: Kind.STRING })).toThrow(/Can only validate floating point numbers as non-negative floating point numbers but got a/); + }); + }); }); }); diff --git a/src/__tests__/NonNegativeInt.test.js b/src/__tests__/NonNegativeInt.test.js index 4bd618290..afe465946 100644 --- a/src/__tests__/NonNegativeInt.test.js +++ b/src/__tests__/NonNegativeInt.test.js @@ -68,36 +68,95 @@ describe('NonNegativeInt', () => { }); describe('invalid', () => { + describe('null', () => { + test('serialize', () => { + expect(() => NonNegativeInt.serialize(null)).toThrow(/Value is not a number/); + }); + + // FIXME: Does nothing. No throw. Call doesn't even seem to get to the parseValue() function. + // test('parseValue', () => { + // expect(() => NonNegativeInt.parseValue(null)).toThrow(/Value is not a number/); + // }); + + test('parseLiteral', () => { + expect(() => NonNegativeInt.parseLiteral({ value: null, kind: Kind.INT })).toThrow(/Value is not a number/); + }); + }); + + describe('undefined', () => { + test('serialize', () => { + expect(() => NonNegativeInt.serialize(undefined)).toThrow(/Value is not a number/); + }); + + // FIXME: Does nothing. No throw. Call doesn't even seem to get to the parseValue() function. + // test('parseValue', () => { + // expect(() => NonNegativeInt.parseValue(undefined)).toThrow(/Value is not a number/); + // }); + + test('parseLiteral', () => { + expect(() => NonNegativeInt.parseLiteral({ value: undefined, kind: Kind.INT })).toThrow(/Value is not a number/); + }); + }); + + describe('unsafe integer', () => { + test('serialize', () => { + expect(() => NonNegativeInt.serialize(2 ** 53)).toThrow(/Value is not a safe integer/); + }); + + // FIXME: Does nothing. No throw. Call doesn't even seem to get to the parseValue() function. + test('parseValue', () => { + expect(() => NonNegativeInt.parseValue(2 ** 53)).toThrow(/Value is not a safe integer/); + }); + + test('parseLiteral', () => { + expect(() => NonNegativeInt.parseLiteral({ value: 2 ** 53, kind: Kind.INT })).toThrow(/Value is not a safe integer/); + }); + }); + describe('less than zero', () => { describe('as int', () => { test('serialize', () => { - expect(() => NonNegativeInt.serialize(-1)).toThrow(/Value is a negative number/); + expect(() => NonNegativeInt.serialize(-1)).toThrow(/Value is not a non-negative number/); }); test('parseValue', () => { - expect(() => NonNegativeInt.parseValue(-1)).toThrow(/Value is a negative number/); + expect(() => NonNegativeInt.parseValue(-1)).toThrow(/Value is not a non-negative number/); }); test('parseLiteral', () => { - expect(() => NonNegativeInt.parseLiteral({ value: -1, kind: Kind.INT })).toThrow(/Value is a negative number/); + expect(() => NonNegativeInt.parseLiteral({ value: -1, kind: Kind.INT })).toThrow(/Value is not a non-negative number/); }); }); describe('as string', () => { test('serialize', () => { - expect(() => NonNegativeInt.serialize('-1')).toThrow(/Value is a negative number/); + expect(() => NonNegativeInt.serialize('-1')).toThrow(/Value is not a non-negative number/); }); test('parseValue', () => { - expect(() => NonNegativeInt.parseValue('-1')).toThrow(/Value is a negative number/); + expect(() => NonNegativeInt.parseValue('-1')).toThrow(/Value is not a non-negative number/); }); test('parseLiteral', () => { - expect(() => NonNegativeInt.parseLiteral({ value: '-1', kind: Kind.INT })).toThrow(/Value is a negative number/); + expect(() => NonNegativeInt.parseLiteral({ value: '-1', kind: Kind.INT })).toThrow(/Value is not a non-negative number/); }); }); }); + describe('infinity', () => { + test('serialize', () => { + expect(() => NonNegativeInt.serialize(Number.POSITIVE_INFINITY)).toThrow(/Value is not a finite number/); + }); + + test('parseValue', () => { + expect(() => NonNegativeInt.parseValue(Number.POSITIVE_INFINITY)).toThrow(/Value is not a finite number/); + }); + + test('parseLiteral', () => { + expect(() => NonNegativeInt.parseLiteral({ value: Number.POSITIVE_INFINITY, kind: Kind.INT })).toThrow(/Value is not a finite number/); + }); + }); + describe('not a number', () => { test('serialize', () => { expect(() => NonNegativeInt.serialize('not a number')).toThrow(/Value is not a number/); @@ -111,5 +170,20 @@ describe('NonNegativeInt', () => { expect(() => NonNegativeInt.parseLiteral({ value: 'not a number', kind: Kind.STRING })).toThrow(/Can only validate integers as non-negative integers but got a/); }); }); + + describe('NaN', () => { + test('serialize', () => { + expect(() => NonNegativeInt.serialize(Number.NaN)).toThrow(/Value is not a number/); + }); + + // FIXME: Does nothing. No throw. Call doesn't even seem to get to the parseValue() function. + // test('parseValue', () => { + // expect(() => NonNegativeInt.parseValue(Number.NaN)).toThrow(/Value is not a number/); + // }); + + test('parseLiteral', () => { + expect(() => NonNegativeInt.parseLiteral({ value: Number.NaN, kind: Kind.STRING })).toThrow(/Can only validate integers as non-negative integers but got a/); + }); + }); }); }); diff --git a/src/__tests__/PositiveFloat.test.js b/src/__tests__/PositiveFloat.test.js index 81a09fffb..86cb41c84 100644 --- a/src/__tests__/PositiveFloat.test.js +++ b/src/__tests__/PositiveFloat.test.js @@ -36,6 +36,36 @@ describe('PositiveFloat', () => { }); describe('invalid', () => { + describe('null', () => { + test('serialize', () => { + expect(() => PositiveFloat.serialize(null)).toThrow(/Value is not a number/); + }); + + // FIXME: Does nothing. No throw. Call doesn't even seem to get to the parseValue() function. + // test('parseValue', () => { + // expect(() => PositiveFloat.parseValue(null)).toThrow(/Value is not a number/); + // }); + + test('parseLiteral', () => { + expect(() => PositiveFloat.parseLiteral({ value: null, kind: Kind.FLOAT })).toThrow(/Value is not a number/); + }); + }); + + describe('undefined', () => { + test('serialize', () => { + expect(() => PositiveFloat.serialize(undefined)).toThrow(/Value is not a number/); + }); + + // FIXME: Does nothing. No throw. Call doesn't even seem to get to the parseValue() function. + // test('parseValue', () => { + // expect(() => PositiveFloat.parseValue(undefined)).toThrow(/Value is not a number/); + // }); + + test('parseLiteral', () => { + expect(() => PositiveFloat.parseLiteral({ value: undefined, kind: Kind.FLOAT })).toThrow(/Value is not a number/); + }); + }); + describe('zero', () => { describe('as float', () => { test('serialize', () => { @@ -96,6 +126,20 @@ describe('PositiveFloat', () => { }); }); + describe('infinity', () => { + test('serialize', () => { + expect(() => PositiveFloat.serialize(Number.POSITIVE_INFINITY)).toThrow(/Value is not a finite number/); + }); + + test('parseValue', () => { + expect(() => PositiveFloat.parseValue(Number.POSITIVE_INFINITY)).toThrow(/Value is not a finite number/); + }); + + test('parseLiteral', () => { + expect(() => PositiveFloat.parseLiteral({ value: Number.POSITIVE_INFINITY, kind: Kind.FLOAT })).toThrow(/Value is not a finite number/); + }); + }); + describe('not a number', () => { test('serialize', () => { expect(() => PositiveFloat.serialize('not a number')).toThrow(/Value is not a number/); @@ -109,5 +153,20 @@ describe('PositiveFloat', () => { expect(() => PositiveFloat.parseLiteral({ value: 'not a number', kind: Kind.STRING })).toThrow(/Can only validate floating point numbers as positive floating point numbers but got a/); }); }); + + describe('NaN', () => { + test('serialize', () => { + expect(() => PositiveFloat.serialize(Number.NaN)).toThrow(/Value is not a number/); + }); + + // FIXME: Does nothing. No throw. Call doesn't even seem to get to the parseValue() function. + // test('parseValue', () => { + // expect(() => PositiveFloat.parseValue(Number.NaN)).toThrow(/Value is not a number/); + // }); + + test('parseLiteral', () => { + expect(() => PositiveFloat.parseLiteral({ value: Number.NaN, kind: Kind.STRING })).toThrow(/Can only validate floating point numbers as positive floating point numbers but got a/); + }); + }); }); }); diff --git a/src/__tests__/PositiveInt.test.js b/src/__tests__/PositiveInt.test.js index a6ad0eebc..d9b253068 100644 --- a/src/__tests__/PositiveInt.test.js +++ b/src/__tests__/PositiveInt.test.js @@ -36,6 +36,51 @@ describe('PositiveInt', () => { }); describe('invalid', () => { + describe('null', () => { + test('serialize', () => { + expect(() => PositiveInt.serialize(null)).toThrow(/Value is not a number: null/); + }); + + // FIXME: Does nothing. No throw. Call doesn't even seem to get to the parseValue() function. + // test('parseValue', () => { + // expect(() => PositiveInt.parseValue(null)).toThrow(/Value is not a number/); + // }); + + test('parseLiteral', () => { + expect(() => PositiveInt.parseLiteral({ value: null, kind: Kind.INT })).toThrow(/Value is not a number: null/); + }); + }); + + describe('undefined', () => { + test('serialize', () => { + expect(() => PositiveInt.serialize(undefined)).toThrow(/Value is not a number: undefined/); + }); + + // FIXME: Does nothing. No throw. Call doesn't even seem to get to the parseValue() function. + // test('parseValue', () => { + // expect(() => PositiveInt.parseValue(undefined)).toThrow(/Value is not a number/); + // }); + + test('parseLiteral', () => { + expect(() => PositiveInt.parseLiteral({ value: undefined, kind: Kind.INT })).toThrow(/Value is not a number: undefined/); + }); + }); + + describe('unsafe integer', () => { + test('serialize', () => { + expect(() => PositiveInt.serialize(2 ** 53)).toThrow(/Value is not a safe integer/); + }); + + // FIXME: Does nothing. No throw. Call doesn't even seem to get to the parseValue() function. + test('parseValue', () => { + expect(() => PositiveInt.parseValue(2 ** 53)).toThrow(/Value is not a safe integer/); + }); + + test('parseLiteral', () => { + expect(() => PositiveInt.parseLiteral({ value: 2 ** 53, kind: Kind.INT })).toThrow(/Value is not a safe integer/); + }); + }); + describe('zero', () => { describe('as int', () => { test('serialize', () => { @@ -96,6 +141,20 @@ describe('PositiveInt', () => { }); }); + describe('infinity', () => { + test('serialize', () => { + expect(() => PositiveInt.serialize(Number.POSITIVE_INFINITY)).toThrow(/Value is not a finite number/); + }); + + test('parseValue', () => { + expect(() => PositiveInt.parseValue(Number.POSITIVE_INFINITY)).toThrow(/Value is not a finite number/); + }); + + test('parseLiteral', () => { + expect(() => PositiveInt.parseLiteral({ value: Number.POSITIVE_INFINITY, kind: Kind.INT })).toThrow(/Value is not a finite number/); + }); + }); + describe('not a number', () => { test('serialize', () => { expect(() => PositiveInt.serialize('not a number')).toThrow(/Value is not a number/); @@ -109,5 +168,20 @@ describe('PositiveInt', () => { expect(() => PositiveInt.parseLiteral({ value: 'not a number', kind: Kind.STRING })).toThrow(/Can only validate integers as positive integers but got a/); }); }); + + describe('NaN', () => { + test('serialize', () => { + expect(() => PositiveInt.serialize(Number.NaN)).toThrow(/Value is not a number/); + }); + + // FIXME: Does nothing. No throw. Call doesn't even seem to get to the parseValue() function. + // test('parseValue', () => { + // expect(() => PositiveInt.parseValue(Number.NaN)).toThrow(/Value is not a number/); + // }); + + test('parseLiteral', () => { + expect(() => PositiveInt.parseLiteral({ value: Number.NaN, kind: Kind.STRING })).toThrow(/Can only validate integers as positive integers but got a/); + }); + }); }); }); diff --git a/src/utilities.js b/src/utilities.js new file mode 100644 index 000000000..c3e325de7 --- /dev/null +++ b/src/utilities.js @@ -0,0 +1,73 @@ +const VALUE_RANGES = { + NEGATIVE: 'NEGATIVE', + NON_NEGATIVE: 'NON_NEGATIVE', + POSITIVE: 'POSITIVE', + NON_POSITIVE: 'NON_POSITIVE', +}; + +const VALUE_TYPES = { + INT: 'int', + FLOAT: 'float', +}; + +// TODO: Consider implementing coercion like this... +// See: https://github.com/graphql/graphql-js/blob/master/src/type/scalars.js#L13 +// See: https://github.com/graphql/graphql-js/blob/master/src/type/scalars.js#L60 + +function _validateInt(value) { + if (!Number.isFinite(value)) { + throw new TypeError(`Value is not a finite number: ${value}`); + } + + if (!Number.isInteger(value)) { + throw new TypeError(`Value is not an integer: ${value}`); + } + + if (!Number.isSafeInteger(value)) { + throw new TypeError(`Value is not a safe integer: ${value}`); + } +} + +function _validateFloat(value) { + if (!Number.isFinite(value)) { + throw new TypeError(`Value is not a finite number: ${value}`); + } +} + +function processValue(value, range, type) { + if (value === null + || typeof value === 'undefined' + || isNaN(value) + || Number.isNaN(value) + || value === Number.NaN) { + throw new TypeError(`Value is not a number: ${value}`); + } + + let parsedValue; + + switch (type) { + case VALUE_TYPES.FLOAT: + parsedValue = parseFloat(value); + _validateFloat(parsedValue); + break; + + case VALUE_TYPES.INT: + parsedValue = parseInt(value, 10); + _validateInt(parsedValue); + break; + + default: + // no -op, return undefined + } + + if ((range === VALUE_RANGES.NEGATIVE && !(parsedValue < 0)) + || (range === VALUE_RANGES.NON_NEGATIVE && !(parsedValue >= 0)) + || (range === VALUE_RANGES.POSITIVE && !(parsedValue > 0)) + || (range === VALUE_RANGES.NON_POSITIVE && !(parsedValue >= 0))) { + throw new TypeError(`Value is not a ${VALUE_RANGES[range].toLowerCase().replace('_', '-')} number: ${value}`); + } + + return parsedValue; +} + +export { processValue, VALUE_RANGES, VALUE_TYPES }; From ed7e9621e56d105b5393fe90caeccab8d39a7198 Mon Sep 17 00:00:00 2001 From: Chris Cuilla Date: Wed, 17 Jan 2018 19:24:16 -0700 Subject: [PATCH 3/9] Added NonPositiveInt and NonPositiveFloat scalars --- CHANGELOG.md | 7 + README.md | 12 ++ src/NonPositiveFloat.js | 31 ++++ src/NonPositiveInt.js | 31 ++++ src/__tests__/NonPositiveFloat.test.js | 172 +++++++++++++++++++++++ src/__tests__/NonPositiveInt.test.js | 187 +++++++++++++++++++++++++ src/index.js | 4 + src/utilities.js | 2 +- 8 files changed, 445 insertions(+), 1 deletion(-) create mode 100644 src/NonPositiveFloat.js create mode 100644 src/NonPositiveInt.js create mode 100644 src/__tests__/NonPositiveFloat.test.js create mode 100644 src/__tests__/NonPositiveInt.test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b3ed7508..1bcff9a16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,16 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ## [0.2.0] - 2018-01-17 +### Changed +- Implemented more strict numeric type checking +- Some type exception messages changed slightly + ### Added - NegativeInt - NegativeFloat +- NonPositiveInt +- NonPositiveFloat +- more tests ## [0.1.0] - 2017-07-14 ### Added diff --git a/README.md b/README.md index d5e0350c3..fc43741b0 100644 --- a/README.md +++ b/README.md @@ -18,10 +18,12 @@ In your schema: ```graphql scalar DateTime +scalar NonPositiveInt scalar PositiveInt scalar NonNegativeInt scalar NegativeInt +scalar NonPositiveFloat scalar PositiveFloat scalar NonNegativeFloat scalar NegativeFloat @@ -35,10 +37,12 @@ In your resolver map, first import them: import { DateTime, + NonPositiveInt, PositiveInt, NonNegativeInt, NegativeInt, + NonPositiveFloat, PositiveFloat, NonNegativeFloat, NegativeFloat, @@ -54,10 +58,12 @@ Then make sure they're in the root resolver map like this: const myResolverMap = { DateTime, + NonPositiveInt, PositiveInt, NonNegativeInt, NegativeInt, + NonPositiveFloat, PositiveFloat, NonNegativeFloat, NegativeFloat, @@ -146,6 +152,9 @@ inevitable parsing or conversion themselves. ### NonNegativeInt Integers that will have a value of 0 or more. Uses [`parseInt()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt). +### NonPositiveInt +Integers that will have a value of 0 or less. Uses [`parseInt()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt). + ### PositiveInt Integers that will have a value greater than 0. Uses [`parseInt()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt). @@ -155,6 +164,9 @@ Integers that will have a value less than 0. Uses [`parseInt()`](https://develop ### NonNegativeFloat Floats that will have a value of 0 or more. Uses [`parseFloat()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseFloat). +### NonPositiveFloat +Floats that will have a value of 0 or less. Uses [`parseFloat()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseFloat). + ### PositiveFloat Floats that will have a value greater than 0. Uses [`parseFloat()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseFloat). diff --git a/src/NonPositiveFloat.js b/src/NonPositiveFloat.js new file mode 100644 index 000000000..59627cf1a --- /dev/null +++ b/src/NonPositiveFloat.js @@ -0,0 +1,31 @@ +import { GraphQLScalarType } from 'graphql'; +import { GraphQLError } from 'graphql/error'; +import { Kind } from 'graphql/language'; + +import { processValue, VALUE_RANGES, VALUE_TYPES } from './utilities'; + +function _processValue(value) { + return processValue(value, VALUE_RANGES.NON_POSITIVE, VALUE_TYPES.FLOAT); +} + +export default new GraphQLScalarType({ + name: 'NonPositiveFloat', + + description: 'Floats that will have a value of 0 or less.', + + serialize(value) { + return _processValue(value); + }, + + parseValue(value) { + return _processValue(value); + }, + + parseLiteral(ast) { + if (ast.kind !== Kind.FLOAT) { + throw new GraphQLError(`Can only validate floating point numbers as non-positive floating point numbers but got a: ${ast.kind}`); // eslint-disable-line max-len + } + + return _processValue(ast.value); + }, +}); diff --git a/src/NonPositiveInt.js b/src/NonPositiveInt.js new file mode 100644 index 000000000..581f549de --- /dev/null +++ b/src/NonPositiveInt.js @@ -0,0 +1,31 @@ +import { GraphQLScalarType } from 'graphql'; +import { GraphQLError } from 'graphql/error'; +import { Kind } from 'graphql/language'; + +import { processValue, VALUE_RANGES, VALUE_TYPES } from './utilities'; + +function _processValue(value) { + return processValue(value, VALUE_RANGES.NON_POSITIVE, VALUE_TYPES.INT); +} + +export default new GraphQLScalarType({ + name: 'NonPositiveInt', + + description: 'Integers that will have a value of 0 or less.', + + serialize(value) { + return _processValue(value); + }, + + parseValue(value) { + return _processValue(value); + }, + + parseLiteral(ast) { + if (ast.kind !== Kind.INT) { + throw new GraphQLError(`Can only validate integers as non-positive integers but got a: ${ast.kind}`); // eslint-disable-line max-len + } + + return _processValue(ast.value); + }, +}); diff --git a/src/__tests__/NonPositiveFloat.test.js b/src/__tests__/NonPositiveFloat.test.js new file mode 100644 index 000000000..02f2be68a --- /dev/null +++ b/src/__tests__/NonPositiveFloat.test.js @@ -0,0 +1,172 @@ +/* global describe, test, expect */ + +import { Kind } from 'graphql/language'; + +import { NonPositiveFloat } from '../'; + +describe('NonPositiveFloat', () => { + describe('valid', () => { + describe('as float', () => { + test('serialize', () => { + expect(NonPositiveFloat.serialize(-123.45)).toBe(-123.45); + }); + + test('parseValue', () => { + expect(NonPositiveFloat.parseValue(-123.45)).toBe(-123.45); + }); + + test('parseLiteral', () => { + expect(NonPositiveFloat.parseLiteral({ value: -123.45, kind: Kind.FLOAT })).toBe(-123.45); + }); + }); + + describe('as string', () => { + test('serialize', () => { + expect(NonPositiveFloat.serialize('-123.45')).toBe(-123.45); + }); + + test('parseValue', () => { + expect(NonPositiveFloat.parseValue('-123.45')).toBe(-123.45); + }); + + test('parseLiteral', () => { + expect(NonPositiveFloat.parseLiteral({ value: '-123.45', kind: Kind.FLOAT })).toBe(-123.45); + }); + }); + + describe('zero', () => { + describe('as float', () => { + test('serialize', () => { + expect(NonPositiveFloat.serialize(0.0)).toBe(0.0); + }); + + test('parseValue', () => { + expect(NonPositiveFloat.parseValue(0.0)).toBe(0.0); + }); + + test('parseLiteral', () => { + expect(NonPositiveFloat.parseLiteral({ value: 0.0, kind: Kind.FLOAT })).toBe(0.0); + }); + }); + + describe('as string', () => { + test('serialize', () => { + expect(NonPositiveFloat.serialize('0.0')).toBe(0.0); + }); + + test('parseValue', () => { + expect(NonPositiveFloat.parseValue('0.0')).toBe(0.0); + }); + + test('parseLiteral', () => { + expect(NonPositiveFloat.parseLiteral({ value: '0.0', kind: Kind.FLOAT })).toBe(0.0); + }); + }); + }); + }); + + describe('invalid', () => { + describe('null', () => { + test('serialize', () => { + expect(() => NonPositiveFloat.serialize(null)).toThrow(/Value is not a number/); + }); + + // FIXME: Does nothing. No throw. Call doesn't even seem to get to the parseValue() function. + // test('parseValue', () => { + // expect(() => NonPositiveFloat.parseValue(null)).toThrow(/Value is not a number/); + // }); + + test('parseLiteral', () => { + expect(() => NonPositiveFloat.parseLiteral({ value: null, kind: Kind.FLOAT })).toThrow(/Value is not a number/); + }); + }); + + describe('undefined', () => { + test('serialize', () => { + expect(() => NonPositiveFloat.serialize(undefined)).toThrow(/Value is not a number/); + }); + + // FIXME: Does nothing. No throw. Call doesn't even seem to get to the parseValue() function. + // test('parseValue', () => { + // expect(() => NonPositiveFloat.parseValue(undefined)).toThrow(/Value is not a number/); + // }); + + test('parseLiteral', () => { + expect(() => NonPositiveFloat.parseLiteral({ value: undefined, kind: Kind.FLOAT })).toThrow(/Value is not a number/); + }); + }); + + describe('more than zero', () => { + describe('as float', () => { + test('serialize', () => { + expect(() => NonPositiveFloat.serialize(1.0)).toThrow(/Value is not a non-positive number/); + }); + + test('parseValue', () => { + expect(() => NonPositiveFloat.parseValue(1.0)).toThrow(/Value is not a non-positive number/); + }); + + test('parseLiteral', () => { + expect(() => NonPositiveFloat.parseLiteral({ value: 1.0, kind: Kind.FLOAT })).toThrow(/Value is not a non-positive number/); + }); + }); + + describe('as string', () => { + test('serialize', () => { + expect(() => NonPositiveFloat.serialize('1.0')).toThrow(/Value is not a non-positive number/); + }); + + test('parseValue', () => { + expect(() => NonPositiveFloat.parseValue('1.0')).toThrow(/Value is not a non-positive number/); + }); + + test('parseLiteral', () => { + expect(() => NonPositiveFloat.parseLiteral({ value: '1.0', kind: Kind.FLOAT })).toThrow(/Value is not a non-positive number/); + }); + }); + }); + + describe('infinity', () => { + test('serialize', () => { + expect(() => NonPositiveFloat.serialize(Number.NEGATIVE_INFINITY)).toThrow(/Value is not a finite number/); + }); + + test('parseValue', () => { + expect(() => NonPositiveFloat.parseValue(Number.NEGATIVE_INFINITY)).toThrow(/Value is not a finite number/); + }); + + test('parseLiteral', () => { + expect(() => NonPositiveFloat.parseLiteral({ value: Number.NEGATIVE_INFINITY, kind: Kind.FLOAT })).toThrow(/Value is not a finite number/); + }); + }); + + describe('not a number', () => { + test('serialize', () => { + expect(() => NonPositiveFloat.serialize('not a number')).toThrow(/Value is not a number/); + }); + + test('parseValue', () => { + expect(() => NonPositiveFloat.parseValue('not a number')).toThrow(/Value is not a number/); + }); + + test('parseLiteral', () => { + expect(() => NonPositiveFloat.parseLiteral({ value: 'not a number', kind: Kind.STRING })).toThrow(/Can only validate floating point numbers as non-positive floating point numbers but got a/); + }); + }); + + describe('NaN', () => { + test('serialize', () => { + expect(() => NonPositiveFloat.serialize(Number.NaN)).toThrow(/Value is not a number/); + }); + + // FIXME: Does nothing. No throw. Call doesn't even seem to get to the parseValue() function. + // test('parseValue', () => { + // expect(() => NonPositiveFloat.parseValue(Number.NaN)).toThrow(/Value is not a number/); + // }); + + test('parseLiteral', () => { + expect(() => NonPositiveFloat.parseLiteral({ value: Number.NaN, kind: Kind.STRING })).toThrow(/Can only validate floating point numbers as non-positive floating point numbers but got a/); + }); + }); + }); +}); diff --git a/src/__tests__/NonPositiveInt.test.js b/src/__tests__/NonPositiveInt.test.js new file mode 100644 index 000000000..ed930b133 --- /dev/null +++ b/src/__tests__/NonPositiveInt.test.js @@ -0,0 +1,187 @@ +/* global describe, test, expect */ + +import { Kind } from 'graphql/language'; + +import { NonPositiveInt } from '../'; + +describe('NonPositiveInt', () => { + describe('valid', () => { + describe('as int', () => { + test('serialize', () => { + expect(NonPositiveInt.serialize(-123)).toBe(-123); + }); + + test('parseValue', () => { + expect(NonPositiveInt.parseValue(-123)).toBe(-123); + }); + + test('parseLiteral', () => { + expect(NonPositiveInt.parseLiteral({ value: -123, kind: Kind.INT })).toBe(-123); + }); + }); + + describe('as string', () => { + test('serialize', () => { + expect(NonPositiveInt.serialize('-123')).toBe(-123); + }); + + test('parseValue', () => { + expect(NonPositiveInt.parseValue('-123')).toBe(-123); + }); + + test('parseLiteral', () => { + expect(NonPositiveInt.parseLiteral({ value: '-123', kind: Kind.INT })).toBe(-123); + }); + }); + + describe('zero', () => { + describe('as int', () => { + test('serialize', () => { + expect(NonPositiveInt.serialize(0)).toBe(0); + }); + + test('parseValue', () => { + expect(NonPositiveInt.parseValue(0)).toBe(0); + }); + + test('parseLiteral', () => { + expect(NonPositiveInt.parseLiteral({ value: 0, kind: Kind.INT })).toBe(0); + }); + }); + + describe('as string', () => { + test('serialize', () => { + expect(NonPositiveInt.serialize('0')).toBe(0); + }); + + test('parseValue', () => { + expect(NonPositiveInt.parseValue('0')).toBe(0); + }); + + test('parseLiteral', () => { + expect(NonPositiveInt.parseLiteral({ value: '0', kind: Kind.INT })).toBe(0); + }); + }); + }); + }); + + describe('invalid', () => { + describe('null', () => { + test('serialize', () => { + expect(() => NonPositiveInt.serialize(null)).toThrow(/Value is not a number/); + }); + + // FIXME: Does nothing. No throw. Call doesn't even seem to get to the parseValue() function. + // test('parseValue', () => { + // expect(() => NonPositiveInt.parseValue(null)).toThrow(/Value is not a number/); + // }); + + test('parseLiteral', () => { + expect(() => NonPositiveInt.parseLiteral({ value: null, kind: Kind.INT })).toThrow(/Value is not a number/); + }); + }); + + describe('undefined', () => { + test('serialize', () => { + expect(() => NonPositiveInt.serialize(undefined)).toThrow(/Value is not a number/); + }); + + // FIXME: Does nothing. No throw. Call doesn't even seem to get to the parseValue() function. + // test('parseValue', () => { + // expect(() => NonPositiveInt.parseValue(undefined)).toThrow(/Value is not a number/); + // }); + + test('parseLiteral', () => { + expect(() => NonPositiveInt.parseLiteral({ value: undefined, kind: Kind.INT })).toThrow(/Value is not a number/); + }); + }); + + describe('unsafe integer', () => { + test('serialize', () => { + expect(() => NonPositiveInt.serialize(2 ** 53)).toThrow(/Value is not a safe integer/); + }); + + // FIXME: Does nothing. No throw. Call doesn't even seem to get to the parseValue() function. + test('parseValue', () => { + expect(() => NonPositiveInt.parseValue(2 ** 53)).toThrow(/Value is not a safe integer/); + }); + + test('parseLiteral', () => { + expect(() => NonPositiveInt.parseLiteral({ value: 2 ** 53, kind: Kind.INT })).toThrow(/Value is not a safe integer/); + }); + }); + + describe('more than zero', () => { + describe('as int', () => { + test('serialize', () => { + expect(() => NonPositiveInt.serialize(1)).toThrow(/Value is not a non-positive number/); + }); + + test('parseValue', () => { + expect(() => NonPositiveInt.parseValue(1)).toThrow(/Value is not a non-positive number/); + }); + + test('parseLiteral', () => { + expect(() => NonPositiveInt.parseLiteral({ value: 1, kind: Kind.INT })).toThrow(/Value is not a non-positive number/); + }); + }); + + describe('as string', () => { + test('serialize', () => { + expect(() => NonPositiveInt.serialize('1')).toThrow(/Value is not a non-positive number/); + }); + + test('parseValue', () => { + expect(() => NonPositiveInt.parseValue('1')).toThrow(/Value is not a non-positive number/); + }); + + test('parseLiteral', () => { + expect(() => NonPositiveInt.parseLiteral({ value: '1', kind: Kind.INT })).toThrow(/Value is not a non-positive number/); + }); + }); + }); + + describe('infinity', () => { + test('serialize', () => { + expect(() => NonPositiveInt.serialize(Number.NEGATIVE_INFINITY)).toThrow(/Value is not a finite number/); + }); + + test('parseValue', () => { + expect(() => NonPositiveInt.parseValue(Number.NEGATIVE_INFINITY)).toThrow(/Value is not a finite number/); + }); + + test('parseLiteral', () => { + expect(() => NonPositiveInt.parseLiteral({ value: Number.NEGATIVE_INFINITY, kind: Kind.INT })).toThrow(/Value is not a finite number/); + }); + }); + + describe('not a number', () => { + test('serialize', () => { + expect(() => NonPositiveInt.serialize('not a number')).toThrow(/Value is not a number/); + }); + + test('parseValue', () => { + expect(() => NonPositiveInt.parseValue('not a number')).toThrow(/Value is not a number/); + }); + + test('parseLiteral', () => { + expect(() => NonPositiveInt.parseLiteral({ value: 'not a number', kind: Kind.STRING })).toThrow(/Can only validate integers as non-positive integers but got a/); + }); + }); + + describe('NaN', () => { + test('serialize', () => { + expect(() => NonPositiveInt.serialize(Number.NaN)).toThrow(/Value is not a number/); + }); + + // FIXME: Does nothing. No throw. Call doesn't even seem to get to the parseValue() function. + // test('parseValue', () => { + // expect(() => NonPositiveInt.parseValue(Number.NaN)).toThrow(/Value is not a number/); + // }); + + test('parseLiteral', () => { + expect(() => NonPositiveInt.parseLiteral({ value: Number.NaN, kind: Kind.STRING })).toThrow(/Can only validate integers as non-positive integers but got a/); + }); + }); + }); +}); diff --git a/src/index.js b/src/index.js index a31aa3adf..2fc823fc9 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,9 @@ import DateTime from './DateTime'; +import NonPositiveInt from './NonPositiveInt'; import PositiveInt from './PositiveInt'; import NonNegativeInt from './NonNegativeInt'; import NegativeInt from './NegativeInt'; +import NonPositiveFloat from './NonPositiveFloat'; import PositiveFloat from './PositiveFloat'; import NonNegativeFloat from './NonNegativeFloat'; import NegativeFloat from './NegativeFloat'; @@ -10,9 +12,11 @@ import URL from './URL'; export { // named DateTime, + NonPositiveInt, PositiveInt, NonNegativeInt, NegativeInt, + NonPositiveFloat, PositiveFloat, NonNegativeFloat, NegativeFloat, diff --git a/src/utilities.js b/src/utilities.js index c3e325de7..e46dddb2f 100644 --- a/src/utilities.js +++ b/src/utilities.js @@ -63,7 +63,7 @@ function processValue(value, range, type) { if ((range === VALUE_RANGES.NEGATIVE && !(parsedValue < 0)) || (range === VALUE_RANGES.NON_NEGATIVE && !(parsedValue >= 0)) || (range === VALUE_RANGES.POSITIVE && !(parsedValue > 0)) - || (range === VALUE_RANGES.NON_POSITIVE && !(parsedValue >= 0))) { + || (range === VALUE_RANGES.NON_POSITIVE && !(parsedValue <= 0))) { throw new TypeError(`Value is not a ${VALUE_RANGES[range].toLowerCase().replace('_', '-')} number: ${value}`); } From 32570368380355d7ab81fdd4dc57bed2fd88ca56 Mon Sep 17 00:00:00 2001 From: Chris Cuilla Date: Fri, 19 Jan 2018 13:02:39 -0700 Subject: [PATCH 4/9] Refactor and streamline call to the processValue function. --- src/NegativeFloat.js | 12 ++++-------- src/NegativeInt.js | 12 ++++-------- src/NonNegativeFloat.js | 12 ++++-------- src/NonNegativeInt.js | 12 ++++-------- src/NonPositiveFloat.js | 12 ++++-------- src/NonPositiveInt.js | 12 ++++-------- src/PositiveFloat.js | 12 ++++-------- src/PositiveInt.js | 12 ++++-------- src/utilities.js | 42 +++++++++++++++++++++++++++++++++++++++-- 9 files changed, 72 insertions(+), 66 deletions(-) diff --git a/src/NegativeFloat.js b/src/NegativeFloat.js index d577ae699..09ea36f53 100644 --- a/src/NegativeFloat.js +++ b/src/NegativeFloat.js @@ -2,11 +2,7 @@ import { GraphQLScalarType } from 'graphql'; import { GraphQLError } from 'graphql/error'; import { Kind } from 'graphql/language'; -import { processValue, VALUE_RANGES, VALUE_TYPES } from './utilities'; - -function _processValue(value) { - return processValue(value, VALUE_RANGES.NEGATIVE, VALUE_TYPES.FLOAT); -} +import { processValue, VALIDATIONS } from './utilities'; export default new GraphQLScalarType({ name: 'NegativeFloat', @@ -14,11 +10,11 @@ export default new GraphQLScalarType({ description: 'Floats that will have a value less than 0.', serialize(value) { - return _processValue(value); + return processValue(value, VALIDATIONS.NegativeFloat); }, parseValue(value) { - return _processValue(value); + return processValue(value, VALIDATIONS.NegativeFloat); }, parseLiteral(ast) { @@ -26,6 +22,6 @@ export default new GraphQLScalarType({ throw new GraphQLError(`Can only validate floating point numbers as negative floating point numbers but got a: ${ast.kind}`); // eslint-disable-line max-len } - return _processValue(ast.value); + return processValue(ast.value, VALIDATIONS.NegativeFloat); }, }); diff --git a/src/NegativeInt.js b/src/NegativeInt.js index 8aa3d6376..eb770aa17 100644 --- a/src/NegativeInt.js +++ b/src/NegativeInt.js @@ -2,11 +2,7 @@ import { GraphQLScalarType } from 'graphql'; import { GraphQLError } from 'graphql/error'; import { Kind } from 'graphql/language'; -import { processValue, VALUE_RANGES, VALUE_TYPES } from './utilities'; - -function _processValue(value) { - return processValue(value, VALUE_RANGES.NEGATIVE, VALUE_TYPES.INT); -} +import { processValue, VALIDATIONS } from './utilities'; export default new GraphQLScalarType({ name: 'NegativeInt', @@ -14,11 +10,11 @@ export default new GraphQLScalarType({ description: 'Integers that will have a value less than 0.', serialize(value) { - return _processValue(value); + return processValue(value, VALIDATIONS.NegativeInt); }, parseValue(value) { - return _processValue(value); + return processValue(value, VALIDATIONS.NegativeInt); }, parseLiteral(ast) { @@ -26,6 +22,6 @@ export default new GraphQLScalarType({ throw new GraphQLError(`Can only validate integers as negative integers but got a: ${ast.kind}`); // eslint-disable-line max-len } - return _processValue(ast.value); + return processValue(ast.value, VALIDATIONS.NegativeInt); }, }); diff --git a/src/NonNegativeFloat.js b/src/NonNegativeFloat.js index 50fe7daa3..6035e0bcb 100644 --- a/src/NonNegativeFloat.js +++ b/src/NonNegativeFloat.js @@ -2,11 +2,7 @@ import { GraphQLScalarType } from 'graphql'; import { GraphQLError } from 'graphql/error'; import { Kind } from 'graphql/language'; -import { processValue, VALUE_RANGES, VALUE_TYPES } from './utilities'; - -function _processValue(value) { - return processValue(value, VALUE_RANGES.NON_NEGATIVE, VALUE_TYPES.FLOAT); -} +import { processValue, VALIDATIONS } from './utilities'; export default new GraphQLScalarType({ name: 'NonNegativeFloat', @@ -14,11 +10,11 @@ export default new GraphQLScalarType({ description: 'Floats that will have a value of 0 or more.', serialize(value) { - return _processValue(value); + return processValue(value, VALIDATIONS.NonNegativeFloat); }, parseValue(value) { - return _processValue(value); + return processValue(value, VALIDATIONS.NonNegativeFloat); }, parseLiteral(ast) { @@ -26,6 +22,6 @@ export default new GraphQLScalarType({ throw new GraphQLError(`Can only validate floating point numbers as non-negative floating point numbers but got a: ${ast.kind}`); // eslint-disable-line max-len } - return _processValue(ast.value); + return processValue(ast.value, VALIDATIONS.NonNegativeFloat); }, }); diff --git a/src/NonNegativeInt.js b/src/NonNegativeInt.js index 5f6f1f836..4fd853539 100644 --- a/src/NonNegativeInt.js +++ b/src/NonNegativeInt.js @@ -2,11 +2,7 @@ import { GraphQLScalarType } from 'graphql'; import { GraphQLError } from 'graphql/error'; import { Kind } from 'graphql/language'; -import { processValue, VALUE_RANGES, VALUE_TYPES } from './utilities'; - -function _processValue(value) { - return processValue(value, VALUE_RANGES.NON_NEGATIVE, VALUE_TYPES.INT); -} +import { processValue, VALIDATIONS } from './utilities'; export default new GraphQLScalarType({ name: 'NonNegativeInt', @@ -14,11 +10,11 @@ export default new GraphQLScalarType({ description: 'Integers that will have a value of 0 or more.', serialize(value) { - return _processValue(value); + return processValue(value, VALIDATIONS.NonNegativeInt); }, parseValue(value) { - return _processValue(value); + return processValue(value, VALIDATIONS.NonNegativeInt); }, parseLiteral(ast) { @@ -26,6 +22,6 @@ export default new GraphQLScalarType({ throw new GraphQLError(`Can only validate integers as non-negative integers but got a: ${ast.kind}`); // eslint-disable-line max-len } - return _processValue(ast.value); + return processValue(ast.value, VALIDATIONS.NonNegativeInt); }, }); diff --git a/src/NonPositiveFloat.js b/src/NonPositiveFloat.js index 59627cf1a..50731f007 100644 --- a/src/NonPositiveFloat.js +++ b/src/NonPositiveFloat.js @@ -2,11 +2,7 @@ import { GraphQLScalarType } from 'graphql'; import { GraphQLError } from 'graphql/error'; import { Kind } from 'graphql/language'; -import { processValue, VALUE_RANGES, VALUE_TYPES } from './utilities'; - -function _processValue(value) { - return processValue(value, VALUE_RANGES.NON_POSITIVE, VALUE_TYPES.FLOAT); -} +import { processValue, VALIDATIONS } from './utilities'; export default new GraphQLScalarType({ name: 'NonPositiveFloat', @@ -14,11 +10,11 @@ export default new GraphQLScalarType({ description: 'Floats that will have a value of 0 or less.', serialize(value) { - return _processValue(value); + return processValue(value, VALIDATIONS.NonPositiveFloat); }, parseValue(value) { - return _processValue(value); + return processValue(value, VALIDATIONS.NonPositiveFloat); }, parseLiteral(ast) { @@ -26,6 +22,6 @@ export default new GraphQLScalarType({ throw new GraphQLError(`Can only validate floating point numbers as non-positive floating point numbers but got a: ${ast.kind}`); // eslint-disable-line max-len } - return _processValue(ast.value); + return processValue(ast.value, VALIDATIONS.NonPositiveFloat); }, }); diff --git a/src/NonPositiveInt.js b/src/NonPositiveInt.js index 581f549de..d7320265c 100644 --- a/src/NonPositiveInt.js +++ b/src/NonPositiveInt.js @@ -2,11 +2,7 @@ import { GraphQLScalarType } from 'graphql'; import { GraphQLError } from 'graphql/error'; import { Kind } from 'graphql/language'; -import { processValue, VALUE_RANGES, VALUE_TYPES } from './utilities'; - -function _processValue(value) { - return processValue(value, VALUE_RANGES.NON_POSITIVE, VALUE_TYPES.INT); -} +import { processValue, VALIDATIONS } from './utilities'; export default new GraphQLScalarType({ name: 'NonPositiveInt', @@ -14,11 +10,11 @@ export default new GraphQLScalarType({ description: 'Integers that will have a value of 0 or less.', serialize(value) { - return _processValue(value); + return processValue(value, VALIDATIONS.NonPositiveInt); }, parseValue(value) { - return _processValue(value); + return processValue(value, VALIDATIONS.NonPositiveInt); }, parseLiteral(ast) { @@ -26,6 +22,6 @@ export default new GraphQLScalarType({ throw new GraphQLError(`Can only validate integers as non-positive integers but got a: ${ast.kind}`); // eslint-disable-line max-len } - return _processValue(ast.value); + return processValue(ast.value, VALIDATIONS.NonPositiveInt); }, }); diff --git a/src/PositiveFloat.js b/src/PositiveFloat.js index 1283b1f1d..4ddc7e748 100644 --- a/src/PositiveFloat.js +++ b/src/PositiveFloat.js @@ -2,11 +2,7 @@ import { GraphQLScalarType } from 'graphql'; import { GraphQLError } from 'graphql/error'; import { Kind } from 'graphql/language'; -import { processValue, VALUE_RANGES, VALUE_TYPES } from './utilities'; - -function _processValue(value) { - return processValue(value, VALUE_RANGES.POSITIVE, VALUE_TYPES.FLOAT); -} +import { processValue, VALIDATIONS } from './utilities'; export default new GraphQLScalarType({ name: 'PositiveFloat', @@ -14,11 +10,11 @@ export default new GraphQLScalarType({ description: 'Floats that will have a value greater than 0.', serialize(value) { - return _processValue(value); + return processValue(value, VALIDATIONS.PositiveFloat); }, parseValue(value) { - return _processValue(value); + return processValue(value, VALIDATIONS.PositiveFloat); }, parseLiteral(ast) { @@ -26,6 +22,6 @@ export default new GraphQLScalarType({ throw new GraphQLError(`Can only validate floating point numbers as positive floating point numbers but got a: ${ast.kind}`); // eslint-disable-line max-len } - return _processValue(ast.value); + return processValue(ast.value, VALIDATIONS.PositiveFloat); }, }); diff --git a/src/PositiveInt.js b/src/PositiveInt.js index e471dae0d..f506a0b07 100644 --- a/src/PositiveInt.js +++ b/src/PositiveInt.js @@ -2,11 +2,7 @@ import { GraphQLScalarType } from 'graphql'; import { GraphQLError } from 'graphql/error'; import { Kind } from 'graphql/language'; -import { processValue, VALUE_RANGES, VALUE_TYPES } from './utilities'; - -function _processValue(value) { - return processValue(value, VALUE_RANGES.POSITIVE, VALUE_TYPES.INT); -} +import { processValue, VALIDATIONS } from './utilities'; export default new GraphQLScalarType({ name: 'PositiveInt', @@ -14,11 +10,11 @@ export default new GraphQLScalarType({ description: 'Integers that will have a value greater than 0.', serialize(value) { - return _processValue(value); + return processValue(value, VALIDATIONS.PositiveInt); }, parseValue(value) { - return _processValue(value); + return processValue(value, VALIDATIONS.PositiveInt); }, parseLiteral(ast) { @@ -26,6 +22,6 @@ export default new GraphQLScalarType({ throw new GraphQLError(`Can only validate integers as positive integers but got a: ${ast.kind}`); // eslint-disable-line max-len } - return _processValue(ast.value); + return processValue(ast.value, VALIDATIONS.PositiveInt); }, }); diff --git a/src/utilities.js b/src/utilities.js index e46dddb2f..6b4e4936e 100644 --- a/src/utilities.js +++ b/src/utilities.js @@ -10,6 +10,42 @@ const VALUE_TYPES = { FLOAT: 'float', }; +const VALIDATIONS = { + NonPositiveInt: { + range: VALUE_RANGES.NON_POSITIVE, + type: VALUE_TYPES.INT, + }, + PositiveInt: { + range: VALUE_RANGES.POSITIVE, + type: VALUE_TYPES.INT, + }, + NonNegativeInt: { + range: VALUE_RANGES.NON_NEGATIVE, + type: VALUE_TYPES.INT, + }, + NegativeInt: { + range: VALUE_RANGES.NEGATIVE, + type: VALUE_TYPES.INT, + }, + + NonPositiveFloat: { + range: VALUE_RANGES.NON_POSITIVE, + type: VALUE_TYPES.FLOAT, + }, + PositiveFloat: { + range: VALUE_RANGES.POSITIVE, + type: VALUE_TYPES.FLOAT, + }, + NonNegativeFloat: { + range: VALUE_RANGES.NON_NEGATIVE, + type: VALUE_TYPES.FLOAT, + }, + NegativeFloat: { + range: VALUE_RANGES.NEGATIVE, + type: VALUE_TYPES.FLOAT, + }, +}; + // TODO: Consider implementing coercion like this... // See: https://github.com/graphql/graphql-js/blob/master/src/type/scalars.js#L13 // See: https://github.com/graphql/graphql-js/blob/master/src/type/scalars.js#L60 @@ -34,7 +70,9 @@ function _validateFloat(value) { } } -function processValue(value, range, type) { +function processValue(value, validation) { + const { range, type } = validation; + if (value === null || typeof value === 'undefined' || isNaN(value) @@ -70,4 +108,4 @@ function processValue(value, range, type) { return parsedValue; } -export { processValue, VALUE_RANGES, VALUE_TYPES }; +export { processValue, VALIDATIONS }; From 21020e98c1be3abfb81d944d65eaae5cea4055f5 Mon Sep 17 00:00:00 2001 From: Chris Cuilla Date: Tue, 6 Feb 2018 20:56:33 -0700 Subject: [PATCH 5/9] Change dependencies to peerDependencies --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ddb51da86..e26537180 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "jest": "^20.0.4", "nodemon": "1.11.x" }, - "dependencies": { + "peerDependencies": { "graphql": "^0.10.1" } } From 0609b5efa212d01d14a8ea37ab3becbaea5bd6c1 Mon Sep 17 00:00:00 2001 From: Chris Cuilla Date: Tue, 6 Feb 2018 21:00:33 -0700 Subject: [PATCH 6/9] Updated version to 0.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e26537180..1f9ca608e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@okgrow/graphql-scalars", - "version": "0.1.0", + "version": "0.2.0", "description": "A collection of scalar types not included in base GraphQL.", "repository": { "type": "git", From ec2a3374369aa350a11224ff22adfe0baddf4825 Mon Sep 17 00:00:00 2001 From: Chris Cuilla Date: Tue, 6 Feb 2018 21:03:15 -0700 Subject: [PATCH 7/9] Added dependency change note to CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bcff9a16..1a1173e84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed - Implemented more strict numeric type checking - Some type exception messages changed slightly +- Changed `graphql` from a dependency to a _peer_ dependency ### Added - NegativeInt From eefd5fda70a0045e1ecbd3bb0cf323e8ee2b31be Mon Sep 17 00:00:00 2001 From: Chris Cuilla Date: Tue, 6 Feb 2018 21:05:37 -0700 Subject: [PATCH 8/9] Added graphql to devDependencies --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 1f9ca608e..11f88b1a6 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "eslint-plugin-import": "^2.2.0", "eslint-plugin-jsx-a11y": "^3.0.0", "eslint-plugin-react": "^6.10.3", + "graphql": "^0.10.1", "jest": "^20.0.4", "nodemon": "1.11.x" }, From b5086635d519d252804da0476431fc023f2cb834 Mon Sep 17 00:00:00 2001 From: Chris Cuilla Date: Fri, 16 Feb 2018 10:22:09 -0700 Subject: [PATCH 9/9] doc: updated change log date for 0.2.0 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a1173e84..7cf6e40c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). -## [0.2.0] - 2018-01-17 +## [0.2.0] - 2018-01-16 ### Changed - Implemented more strict numeric type checking - Some type exception messages changed slightly