diff --git a/.gitignore b/.gitignore index ad46b30..b55295f 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,6 @@ typings/ # next.js build output .next + +# JetBrains IDE's project config folder +.idea diff --git a/README.md b/README.md index 185ab8a..b4e0b84 100644 --- a/README.md +++ b/README.md @@ -32,3 +32,10 @@ Parses a parameter string of the format key1=value1&key2=value2, optionally conv If the value types are to be converted, true and false are converted to booleans irrespective of case. Values are converted to numbers if they match the following regex ```/^[-+]?(\d+\.)?\d+(E[-+]?\d+)?$/i```. If you need finer control of value conversions, leave parseValues at false and handle the conversions in your driver code. + +If the driver supports multiple levels to the config object (like the MySQL driver), you can modify the `key` as follows: +- `key1=value1&key2[subkey1]=value2` yields `{ key1: "value1", key2: { subkey1: "value2" } }` +- `key1=value1&key2[subkey1][subsubkey1]=value2` yields `{ key1: "value1", key2: { subkey1: { subkey2: "value2" } } }` +- `key1=value1&key2[]=value2` yields `{ key1: "value1", key2: [ "value2" ] }` +- `key1=value1&key2[]=value2&key2[]=value3` yields `{ key1: "value1", key2: [ "value2", "value3" ] }` +- `key1=value1&key2[0]=value2` yields `{ key1: "value1", key2: [ "value2" ] }` diff --git a/index.js b/index.js new file mode 100644 index 0000000..67577b2 --- /dev/null +++ b/index.js @@ -0,0 +1,46 @@ +var keyBreaker = /([^\[\]]+)|(\[\])/g, + digitTest = /^\d+$/; + +function parseConnectionParams(paramstring, parseValues = false) { + const params = {}; + const numberRegex = /^[-+]?(\d+\.)?\d+(E[-+]?\d+)?$/i; + + if (paramstring) { + const valuesPreprocessor = parseValues ? + (value) => { + const lowerCasedValue = value.toLowerCase(); + if (lowerCasedValue === 'true') value = true; + else if (lowerCasedValue === 'false') value = false; + else if (numberRegex.test(value)) value = parseFloat(value); + return value; + } : + (value) => value; + + // based on $.String.deparam: https://github.com/jupiterjs/jquerymx/blob/master/lang/string/deparam/deparam.js + paramstring.split('&').map(s => { + let [key, val] = s.split('='); + let current = params; + let parts = key.match(keyBreaker); + for(var i = 0; i < parts.length - 1; i++) { + let part = parts[i]; + if(!current[part]) { + // if what we are pointing to looks like an array + current[part] = digitTest.test(parts[i+1]) || parts[i+1] == "[]" ? [] : {} + } + current = current[part]; + } + let lastPart = parts[parts.length - 1]; + if(lastPart == "[]"){ + current.push(valuesPreprocessor(val)); + } else { + current[lastPart] = valuesPreprocessor(val); + } + }); + } + + return params; +} + +module.exports = { + parseConnectionParams +}; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..27573a3 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,196 @@ +{ + "name": "database-js-common", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "1.1.11" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "dev": true, + "requires": { + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.5", + "he": "1.1.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "supports-color": "5.4.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..6246c7c --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "database-js-common", + "version": "1.0.1", + "description": "database-js common code", + "main": "index.js", + "scripts": { + "test": "./node_modules/.bin/mocha --reporter spec" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/mlaanderson/database-js-common.git" + }, + "keywords": [ + "database-js" + ], + "author": "Michael Anderson ", + "license": "MIT", + "bugs": { + "url": "https://github.com/mlaanderson/database-js-common/issues" + }, + "homepage": "https://github.com/mlaanderson/database-js-common#readme", + "devDependencies": { + "mocha": "^5.2.0" + } +} diff --git a/test/index.test.js b/test/index.test.js new file mode 100644 index 0000000..b50e706 --- /dev/null +++ b/test/index.test.js @@ -0,0 +1,172 @@ +const assert = require('assert'); + +describe('Test parseConnectionParams method', function () { + const parseConnectionParams = require('../.').parseConnectionParams; + const testedParamString = 'booleanTrue=true&booleanFalse=false&string=foo&integer=35&float=35.8'; + const testedSingleNestedObjectString = 'subkey[string]=foo&subkey[booleanTrue]=true&subkey[booleanFalse]=false&subkey[integer]=42&subkey[float]=98.6&array[0]=0&array[1]=98.6&array2[]=0&array2[]=98.6'; + const testedMultiNestedObjectString = 'subkey[subkey][string]=foo&subkey[subkey][booleanTrue]=true&subkey[subkey][booleanFalse]=false&subkey[subkey][integer]=42&subkey[subkey][float]=98.6&subkey[subkey][array][]=0&subkey[subkey][array][]=98.6&array[0][0]=0&array[0][1]=98.6&array[1][]=0&array[1][]=98.6'; + + describe('Test default not parsed values', function () { + const result = parseConnectionParams(testedParamString); + + it('should be string true', function () { + assert.equal(result.booleanTrue, 'true') + }); + + it('should be string false', function () { + assert.equal(result.booleanFalse, 'false') + }); + + + it('should be string', function () { + assert.equal(result.string, 'foo') + }); + + + it('should be string number', function () { + assert.equal(result.integer, '35') + }); + + it('should be string float', function () { + assert.equal(result.float, '35.8') + }); + }); + + it('should return empty object', function () { + const result = parseConnectionParams('', true); + assert.deepEqual(result, {}) + }); + + describe('Test parsed values', function () { + const result = parseConnectionParams(testedParamString, true); + + it('should be parsed', function () { + assert.ok(typeof result === 'object', 'result not parsed'); + }); + + it('should be boolean true', function () { + assert.equal(result.booleanTrue, true) + }); + + it('should be boolean false', function () { + assert.equal(result.booleanFalse, false) + }); + + it('should be string', function () { + assert.equal(result.string, 'foo') + }); + + it('should be integer', function () { + assert.equal(result.integer, 35) + }); + + it('should be float', function () { + assert.equal(result.float, 35.8) + }); + }); + + describe('Test parsing single-level nested objects', function () { + const result = parseConnectionParams(testedSingleNestedObjectString, true); + + it('should be parsed', function () { + assert.ok(typeof result === 'object', 'result not parsed'); + }); + + it('should have object subkey', function () { + assert.ok(typeof result.subkey === 'object', 'result subkey is not an object'); + }); + + it('subkey should be boolean true', function () { + assert.equal(result.subkey.booleanTrue, true); + }); + + it('subkey should be boolean false', function () { + assert.equal(result.subkey.booleanFalse, false); + }); + + it('subkey should be string', function () { + assert.equal(result.subkey.string, 'foo'); + }); + + it('subkey should be integer', function () { + assert.equal(result.subkey.integer, 42); + }); + + it('subkey should be float', function () { + assert.equal(result.subkey.float, 98.6); + }); + + it('should have array subkey', function () { + assert.ok(result.array.constructor === Array, 'result array is not an array'); + }); + + it('array length should be 2', function () { + assert.equal(result.array.length, 2); + }); + + it('array item should be integer', function () { + assert.equal(result.array[0], 0); + }); + + it('array item should be float', function () { + assert.equal(result.array[1], 98.6); + }); + + it('should have array2 subkey', function () { + assert.ok(result.array2.constructor === Array, 'result array2 is not an array'); + }); + + it('array2 length should be 2', function () { + assert.equal(result.array2.length, 2); + }); + + it('array2 item should be integer', function () { + assert.equal(result.array2[0], 0); + }); + + it('array2 item should be float', function () { + assert.equal(result.array2[1], 98.6); + }); + }); + + describe('Test parsing multi-level nested objects', function () { + const result = parseConnectionParams(testedMultiNestedObjectString, true); + + it('should be parsed', function () { + assert.ok(typeof result === 'object', 'result not parsed'); + }); + + it('should have object subkey', function () { + assert.ok(typeof result.subkey === 'object', 'result subkey is not an object'); + }); + + it('should have object subkey object subkey', function () { + assert.ok(typeof result.subkey.subkey === 'object', 'result subkey subkey is not an object'); + }); + + it('subkey should be boolean true', function () { + assert.equal(result.subkey.subkey.booleanTrue, true); + }); + + it('subkey should be boolean false', function () { + assert.equal(result.subkey.subkey.booleanFalse, false); + }); + + it('subkey should be string', function () { + assert.equal(result.subkey.subkey.string, 'foo'); + }); + + it('subkey should be integer', function () { + assert.equal(result.subkey.subkey.integer, 42); + }); + + it('subkey should be float', function () { + assert.equal(result.subkey.subkey.float, 98.6); + }); + + it('subkey should be array', function () { + assert.ok(result.subkey.subkey.array.constructor === Array, 'result subkey array is not an array'); + }); + }); + +});