diff --git a/.gitignore b/.gitignore index 86aaedee1..382feba65 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ yarn.lock /index.js validator.js validator.min.js +.idea diff --git a/README.md b/README.md index 366036d43..41b7df69a 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,7 @@ Validator | Description **isFQDN(str [, options])** | check if the string is a fully qualified domain name (e.g. domain.com).

`options` is an object which defaults to `{ require_tld: true, allow_underscores: false, allow_trailing_dot: false, allow_numeric_tld: false, allow_wildcard: false, ignore_max_length: false }`.

`require_tld` - If set to false the validator will not check if the domain includes a TLD.
`allow_underscores` - if set to true, the validator will allow underscores in the domain.
`allow_trailing_dot` - if set to true, the validator will allow the domain to end with a `.` character.
`allow_numeric_tld` - if set to true, the validator will allow the TLD of the domain to be made up solely of numbers.
`allow_wildcard` - if set to true, the validator will allow domains starting with `*.` (e.g. `*.example.com` or `*.shop.example.com`).
`ignore_max_length` - if set to true, the validator will not check for the standard max length of a domain.
**isFreightContainerID(str)** | alias for `isISO6346`, check if the string is a valid [ISO 6346](https://en.wikipedia.org/wiki/ISO_6346) shipping container identification. **isFullWidth(str)** | check if the string contains any full-width chars. +**isValidGraphQLQuery(str)** | check if the string is valid graphql query (note: uses parse function from graphql package). **isHalfWidth(str)** | check if the string contains any half-width chars. **isHash(str, algorithm)** | check if the string is a hash of type algorithm.

Algorithm is one of `['crc32', 'crc32b', 'md4', 'md5', 'ripemd128', 'ripemd160', 'sha1', 'sha256', 'sha384', 'sha512', 'tiger128', 'tiger160', 'tiger192']`. **isHexadecimal(str)** | check if the string is a hexadecimal number. diff --git a/package.json b/package.json index 7e84ef71d..7badc3308 100644 --- a/package.json +++ b/package.json @@ -67,10 +67,13 @@ "build:node": "babel src -d .", "build": "run-p build:*", "pretest": "npm run build && npm run lint", - "test": "nyc --reporter=cobertura --reporter=text-summary mocha --require @babel/register --reporter dot --recursive" + "test": "nyc --reporter=cobertura --reporter=text-summary mocha --require @babel/register --require ./test/setup.js --reporter dot --recursive" }, "engines": { "node": ">= 0.10" }, - "license": "MIT" + "license": "MIT", + "dependencies": { + "graphql": "^16.11.0" + } } diff --git a/src/index.js b/src/index.js index 87be7113c..57d205fa2 100644 --- a/src/index.js +++ b/src/index.js @@ -129,6 +129,7 @@ import isLicensePlate from './lib/isLicensePlate'; import isStrongPassword from './lib/isStrongPassword'; import isVAT from './lib/isVAT'; +import isValidGraphQLQuery from './lib/isValidGraphQLQuery'; const version = '13.15.15'; @@ -245,6 +246,7 @@ const validator = { isLicensePlate, isVAT, ibanLocales, + isValidGraphQLQuery, }; export default validator; diff --git a/src/lib/isValidGraphQLQuery.js b/src/lib/isValidGraphQLQuery.js new file mode 100644 index 000000000..98a6d434b --- /dev/null +++ b/src/lib/isValidGraphQLQuery.js @@ -0,0 +1,42 @@ +import assertString from './util/assertString'; + +let isGraphQLAvailable = false; +let parseFunction = null; + +// Attempt to load GraphQL parse function +/* istanbul ignore if */ +if (typeof process === 'undefined' || !process.versions || !process.versions.node) { + // Skip initialization in non-Node environments +} else { + const nodeVersion = process.versions.node.split('.')[0]; + /* istanbul ignore else */ + if (parseInt(nodeVersion, 10) >= 10) { + try { + // eslint-disable-next-line global-require + const { parse } = require('graphql'); + parseFunction = parse; + isGraphQLAvailable = true; + } catch (e) { + /* istanbul ignore next */ + // GraphQL loading failed + isGraphQLAvailable = false; + } + } +} + +export default function isValidGraphQLQuery(input) { + assertString(input); + + /* istanbul ignore if */ + if (!isGraphQLAvailable || !parseFunction) { + /* istanbul ignore next */ + return false; + } + + try { + const obj = parseFunction(input); + return (!!obj && typeof obj === 'object'); + } catch (e) { + return false; + } +} diff --git a/test/setup.js b/test/setup.js new file mode 100644 index 000000000..236ed6f7e --- /dev/null +++ b/test/setup.js @@ -0,0 +1,14 @@ +// Polyfill for globalThis (needed for Node < 12) +if (typeof globalThis === 'undefined') { + (function () { + if (typeof global !== 'undefined') { + global.globalThis = global; + } else if (typeof window !== 'undefined') { + window.globalThis = window; + } else if (typeof self !== 'undefined') { + self.globalThis = self; + } else { + throw new Error('Unable to locate global object'); + } + }()); +} diff --git a/test/validators.test.js b/test/validators.test.js index 299af27d8..2d6682ac4 100644 --- a/test/validators.test.js +++ b/test/validators.test.js @@ -7140,8 +7140,15 @@ describe('Validators', () => { it('should define the module using an AMD-compatible loader', () => { let window = { validator: null, - define(module) { - window.validator = module(); + define(deps, factory) { + // Handle AMD define with dependencies + if (Array.isArray(deps) && typeof factory === 'function') { + // Mock the graphql dependency as null/undefined since it's optional + window.validator = factory(null); + } else if (typeof deps === 'function') { + // Handle define without dependencies + window.validator = deps(); + } }, }; window.define.amd = true; @@ -15652,4 +15659,31 @@ describe('Validators', () => { ], }); }); + it('should validate graphQL', () => { + // Skip test on Node.js < 10 due to graphql module incompatibility + const nodeVersion = parseInt(process.version.match(/^v(\d+)/)[1], 10); + if (nodeVersion < 10) { + console.log(' ⚠️ Skipping GraphQL test on Node.js', process.version); + return; + } + + test({ + validator: 'isValidGraphQLQuery', + valid: [ + 'query StudentName {\n' + + ' student {\n' + + ' name\n' + + ' }\n' + + ' }', + ], + invalid: [ + 'query StudentName {\n' + + ' student {\n' + + ' name\n' + + ' }', + 'null', + '2432', + ], + }); + }); });