} row\n\t * @private\n\t * @return {Array}\n\t */\n\tfunction _getClearRow(row) {\n\t\treturn row.map(columnValue => _clearValue(columnValue));\n\t}\n\n\t/**\n\t * Remove BOM character if value is a string\n\t * @param {String} value\n\t * @private\n\t * @return {String}\n\t */\n\tfunction _clearValue(value) {\n\t\treturn _isString(value)\n\t\t\t? value.replace(/^\\ufeff/g, '')\n\t\t\t: value;\n\t}\n\n\t/**\n\t * @param {String|*} value\n\t * @private\n\t * @return {boolean}\n\t */\n\tfunction _isEmpty(value) {\n\t\tif (_isString(value)) {\n\t\t\treturn !(!!value.trim().length);\n\t\t}\n\n\t\treturn _isNil(value);\n\t}\n\n\t/**\n\t * Convert column number to column letter\n\t * @param {Number} columnNumber\n\t * @private\n\t * @return {String}\n\t */\n\tfunction _convertColumnNumberToLetter(columnNumber) {\n\t\tlet columnLetter = '';\n\n\t\t// Loop through the column number, starting with the least significant digit\n\t\twhile (columnNumber > 0) {\n\t\t\t// Get the least significant digit and add 1 to it (since 'A' is the first letter)\n\t\t\tlet digit = (columnNumber - 1) % 26 + 1;\n\n\t\t\t// Convert the digit to the corresponding letter and add it to the beginning of the string\n\t\t\tcolumnLetter = String.fromCharCode(digit + 64) + columnLetter;\n\n\t\t\t// Divide the column number by 26 and discard the remainder to move on to the next digit\n\t\t\tcolumnNumber = Math.floor((columnNumber - 1) / 26);\n\t\t}\n\n\t\treturn columnLetter;\n\t}\n\n\treturn CSVFileValidator;\n})));\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\tid: moduleId,\n\t\tloaded: false,\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n\t// Flag the module as loaded\n\tmodule.loaded = true;\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","__webpack_require__.nmd = (module) => {\n\tmodule.paths = [];\n\tif (!module.children) module.children = [];\n\treturn module;\n};","import CSVFileValidator from '../src/csv-file-validator'\n\nconst requiredError = (headerName, rowNumber, columnNumber) => {\n\treturn `${headerName} is required in the ${rowNumber} row / ${columnNumber} column
`\n}\n\nconst validateError = (headerName, rowNumber, columnNumber) => {\n\treturn `${headerName} is not valid in the ${rowNumber} row / ${columnNumber} column
`\n}\n\nconst dependentValidateError = (headerName, rowNumber, columnNumber) => (\n\t`${headerName} is not valid. Country should be set to Ukraine or role is not user. ${rowNumber} row / ${columnNumber} column
`\n)\n\nconst isRoleForCountryValid = (country, row) => {\n\tconst role = row[4];\n\tconsole.log(\"role\", role)\n\treturn country === 'Ukraine' && role === 'user';\n}\n\nconst uniqueError = (headerName, rowNumber) => {\n\treturn `${headerName} is not unique at the ${rowNumber} row
`\n}\n\nconst isEmailValid = function (email) {\n\tconst reqExp = /[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,3}$/\n\treturn reqExp.test(email)\n}\n\nconst isAgeValid = function (age) {\n\treturn age > 0\n}\n\nconst isPasswordValid = function (password) {\n\treturn password.length >= 4\n}\n\nconst CSVConfig = {\n\theaders: [\n\t\t{ name: 'First Name', inputName: 'firstName', required: true, requiredError },\n\t\t{ name: 'Last Name', inputName: 'lastName', required: true, requiredError, optional: true },\n\t\t{ name: 'Email', inputName: 'email', required: true, requiredError, unique: true, uniqueError, validate: isEmailValid, validateError },\n\t\t{ name: 'Password', inputName: 'password', required: true, requiredError, validate: isPasswordValid, validateError },\n\t\t{ name: 'Roles', inputName: 'roles', required: true, requiredError, isArray: true },\n\t\t{ name: 'Country', inputName: 'country', optional: true, dependentValidate: isRoleForCountryValid, dependentValidateError }\n\t],\n\tisColumnIndexAlphabetic: true\n}\n\ndocument.getElementById('file').onchange = function (event) {\n\tCSVFileValidator(event.target.files[0], CSVConfig)\n\t\t.then(csvData => {\n\t\t\tcsvData.inValidData.forEach(item => {\n\t\t\t\tdocument.getElementById('invalidMessages').insertAdjacentHTML('beforeend', item.message)\n\t\t\t})\n\t\t\tconsole.log(csvData.inValidData)\n\t\t\tconsole.log(csvData.data)\n\t\t})\n}\n\nconst CSVConfig_1 = {\n\theaders: [\n\t\t{ name: 'Name', inputName: 'name', required: true, requiredError },\n\t\t{ name: 'Surname', inputName: 'surname', required: true, requiredError, optional: true },\n\t\t{ name: 'Age', inputName: 'age', required: true, requiredError, validate: isAgeValid, validateError },\n\t],\n\tparserConfig: {\n\t\tdynamicTyping: true\n\t}\n}\n\ndocument.getElementById('file_1').onchange = function (event) {\n\tCSVFileValidator(event.target.files[0], CSVConfig_1)\n\t\t.then(csvData => {\n\t\t\tcsvData.inValidData.forEach(item => {\n\t\t\t\tdocument.getElementById('invalidMessages_1').insertAdjacentHTML('beforeend', item.message)\n\t\t\t})\n\t\t\tconsole.log(csvData.inValidData)\n\t\t\tconsole.log(csvData.data)\n\t\t})\n}\n"],"names":["_uniqBy","module","exports","array","keyName","length","DataView","getNative","hashClear","hashDelete","hashGet","hashHas","hashSet","Hash","entries","index","this","clear","entry","set","prototype","get","has","listCacheClear","listCacheDelete","listCacheGet","listCacheHas","listCacheSet","ListCache","Map","mapCacheClear","mapCacheDelete","mapCacheGet","mapCacheHas","mapCacheSet","MapCache","Promise","Set","setCacheAdd","setCacheHas","SetCache","values","__data__","add","push","stackClear","stackDelete","stackGet","stackHas","stackSet","Stack","data","size","Symbol","Uint8Array","WeakMap","predicate","resIndex","result","value","baseIndexOf","comparator","baseTimes","isArguments","isArray","isBuffer","isIndex","isTypedArray","hasOwnProperty","Object","inherited","isArr","isArg","isBuff","isType","skipIndexes","String","key","call","iteratee","Array","offset","eq","fromIndex","fromRight","castPath","toKey","object","path","undefined","arrayPush","keysFunc","symbolsFunc","getRawTag","objectToString","symToStringTag","toStringTag","baseFindIndex","baseIsNaN","strictIndexOf","baseGetTag","isObjectLike","baseIsEqualDeep","baseIsEqual","other","bitmask","customizer","stack","equalArrays","equalByTag","equalObjects","getTag","argsTag","arrayTag","objectTag","equalFunc","objIsArr","othIsArr","objTag","othTag","objIsObj","othIsObj","isSameTag","objIsWrapped","othIsWrapped","objUnwrapped","othUnwrapped","source","matchData","noCustomizer","objValue","srcValue","COMPARE_PARTIAL_FLAG","isFunction","isMasked","isObject","toSource","reIsHostCtor","funcProto","Function","objectProto","funcToString","toString","reIsNative","RegExp","replace","test","isLength","typedArrayTags","baseMatches","baseMatchesProperty","identity","property","isPrototype","nativeKeys","baseIsMatch","getMatchData","matchesStrictComparable","hasIn","isKey","isStrictComparable","baseGet","n","arrayMap","isSymbol","symbolProto","symbolToString","baseToString","func","arrayIncludes","arrayIncludesWith","cacheHas","createSet","setToArray","includes","isCommon","seen","outer","computed","seenIndex","cache","stringToPath","coreJsData","noop","arraySome","isPartial","arrLength","othLength","arrStacked","othStacked","arrValue","othValue","compared","othIndex","mapToArray","symbolValueOf","valueOf","tag","byteLength","byteOffset","buffer","name","message","convert","stacked","getAllKeys","objProps","objLength","objStacked","skipCtor","objCtor","constructor","othCtor","freeGlobal","g","baseGetAllKeys","getSymbols","keys","isKeyable","map","baseIsNative","getValue","nativeObjectToString","isOwn","unmasked","e","arrayFilter","stubArray","propertyIsEnumerable","nativeGetSymbols","getOwnPropertySymbols","symbol","mapTag","promiseTag","setTag","weakMapTag","dataViewTag","dataViewCtorString","mapCtorString","promiseCtorString","setCtorString","weakMapCtorString","ArrayBuffer","resolve","Ctor","ctorString","hasFunc","nativeCreate","reIsUint","type","reIsDeepProp","reIsPlainProp","uid","maskSrcKey","exec","IE_PROTO","assocIndexOf","splice","pop","getMapData","forEach","memoize","overArg","freeExports","nodeType","freeModule","freeProcess","process","nodeUtil","require","types","binding","transform","arg","freeSelf","self","root","pairs","LARGE_ARRAY_SIZE","memoizeCapped","rePropName","reEscapeChar","string","charCodeAt","match","number","quote","subString","defaultValue","baseHasIn","hasPath","baseIsArguments","arguments","stubFalse","Buffer","baseIsTypedArray","baseUnary","nodeIsTypedArray","arrayLikeKeys","baseKeys","isArrayLike","FUNC_ERROR_TEXT","resolver","TypeError","memoized","args","apply","Cache","baseProperty","basePropertyDeep","baseIteratee","baseUniq","s","f","window","document","postMessage","o","IS_PAPA_WORKER","a","u","b","parse","t","r","dynamicTyping","J","dynamicTypingFunction","worker","WORKERS_SUPPORTED","i","URL","webkitURL","BLOB_URL","createObjectURL","Blob","Worker","onmessage","_","id","userStep","step","userChunk","chunk","userComplete","complete","userError","error","input","config","workerId","NODE_STREAM_INPUT","slice","download","l","p","readable","read","on","File","c","stream","unparse","m","y","delimiter","BAD_DELIMITERS","filter","indexOf","quotes","skipEmptyLines","newline","quoteChar","header","columns","Error","escapeChar","escapeFormulae","Q","JSON","h","fields","meta","v","join","trim","d","Date","stringify","charAt","RECORD_SEP","fromCharCode","UNIT_SEP","BYTE_ORDER_MARK","LocalChunkSize","RemoteChunkSize","DefaultDelimiter","Parser","E","ParserHandle","NetworkStreamer","FileStreamer","StringStreamer","ReadableStreamStreamer","jQuery","fn","each","prop","toUpperCase","attr","toLowerCase","FileReader","files","file","inputElem","instanceConfig","extend","before","action","reason","_handle","_finished","_completed","_halted","_input","_baseIndex","_partialLine","_rowCount","_start","_nextChunk","isFirstChunk","_completeResults","errors","w","chunkSize","parseInt","streamer","_config","parseChunk","beforeFirstChunk","paused","aborted","cursor","substring","preview","results","WORKER_ID","finished","concat","_sendError","_readChunk","_chunkLoaded","XMLHttpRequest","withCredentials","onload","onerror","_chunkError","open","downloadRequestBody","downloadRequestHeaders","setRequestHeader","send","status","readyState","responseText","getResponseHeader","lastIndexOf","statusText","webkitSlice","mozSlice","FileReaderSync","Math","min","readAsText","encoding","target","pause","resume","_streamData","_streamEnd","_streamError","_checkIsFinished","shift","_streamCleanUp","removeListener","pow","abort","k","transformHeader","parseFloat","code","row","split","comments","abs","successful","bestDelimiter","delimitersToGuess","getCharIndex","setTimeout","j","z","M","P","U","q","N","B","fastMode","K","W","H","L","I","F","R","C","S","D","T","O","A","substr","x","linebreak","truncated","terminate","create","Papa","_isFunction","_isString","_isNil","isValuesUnique","_clearValue","csvFile","reject","headers","inValidData","parserConfig","csvData","rowIndex","columnData","isHeaderNameOptional","headerIndex","columnValue","valueConfig","columnIndex","columnNumber","columnLetter","digit","floor","_convertColumnNumberToLetter","headerError","required","requiredError","validate","validateError","dependentValidate","_getClearRow","dependentValidateError","optional","inputName","unique","duplicates","uniqueError","_checkUniqueFields","_prepareDataAndValidateFile","factory","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","loaded","__webpack_modules__","getter","__esModule","definition","defineProperty","enumerable","globalThis","obj","nmd","paths","children","headerName","rowNumber","CSVConfig","email","password","country","role","console","log","isColumnIndexAlphabetic","getElementById","onchange","event","then","item","insertAdjacentHTML","CSVConfig_1","age"],"sourceRoot":""}
\ No newline at end of file
diff --git a/demo/index.js b/demo/index.js
index 60efbcf..3276c77 100644
--- a/demo/index.js
+++ b/demo/index.js
@@ -8,6 +8,15 @@ const validateError = (headerName, rowNumber, columnNumber) => {
return `${headerName} is not valid in the ${rowNumber} row / ${columnNumber} column
`
}
+const dependentValidateError = (headerName, rowNumber, columnNumber) => (
+ `${headerName} is not valid. Country should be set to Ukraine or role is not user. ${rowNumber} row / ${columnNumber} column
`
+)
+
+const isRoleForCountryValid = (country, row) => {
+ const role = row[4];
+ return country === 'Ukraine' && role === 'user';
+}
+
const uniqueError = (headerName, rowNumber) => {
return `${headerName} is not unique at the ${rowNumber} row
`
}
@@ -31,7 +40,8 @@ const CSVConfig = {
{ name: 'Last Name', inputName: 'lastName', required: true, requiredError, optional: true },
{ name: 'Email', inputName: 'email', required: true, requiredError, unique: true, uniqueError, validate: isEmailValid, validateError },
{ name: 'Password', inputName: 'password', required: true, requiredError, validate: isPasswordValid, validateError },
- { name: 'Roles', inputName: 'roles', required: true, requiredError, isArray: true }
+ { name: 'Roles', inputName: 'roles', required: true, requiredError, isArray: true },
+ { name: 'Country', inputName: 'country', optional: true, dependentValidate: isRoleForCountryValid, dependentValidateError }
],
isColumnIndexAlphabetic: true
}
diff --git a/package.json b/package.json
index d5f8263..478d094 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "csv-file-validator",
- "version": "2.1.0",
+ "version": "2.2.0",
"description": "Validation of CSV file against user defined schema (returns back object with data and invalid messages)",
"main": "./src/csv-file-validator.js",
"types": "./src/csv-file-validator.d.ts",
@@ -34,10 +34,10 @@
"dependencies": {
"famulus": "^2.2.3",
"lodash": "^4.17.21",
- "papaparse": "^5.3.2"
+ "papaparse": "^5.4.1"
},
"devDependencies": {
- "@types/papaparse": "^5.3.2",
+ "@types/papaparse": "^5.3.15",
"ava": "^0.25.0",
"codecov.io": "^0.1.6",
"nyc": "^11.4.1",
diff --git a/src/csv-file-validator.d.ts b/src/csv-file-validator.d.ts
index 710cd17..7426023 100644
--- a/src/csv-file-validator.d.ts
+++ b/src/csv-file-validator.d.ts
@@ -50,6 +50,13 @@ export interface FieldSchema {
*/
validate?: (field: string|number|boolean) => boolean;
+ /**
+ * If validate returns false validateError function
+ * will be called with arguments headerName, rowNumber, columnNumber.
+ */
+ validateError?: (headerName: string, rowNumber: number,
+ columnNumber: number) => string;
+
/**
* Validate column value that depends on other values in other columns.
* Must return true for valid field and false for invalid.
@@ -57,11 +64,11 @@ export interface FieldSchema {
dependentValidate?: (field: string, row: [string]) => boolean;
/**
- * If validate returns false validateError function
+ * If dependentValidate returns false dependentValidateError function
* will be called with arguments headerName, rowNumber, columnNumber.
*/
- validateError?: (headerName: string, rowNumber: number,
- columnNumber: number) => string;
+ dependentValidateError?: (headerName: string, rowNumber: number,
+ columnNumber: number) => string;
}
export interface RowError {
diff --git a/src/csv-file-validator.js b/src/csv-file-validator.js
index 1daabe7..143d0ab 100644
--- a/src/csv-file-validator.js
+++ b/src/csv-file-validator.js
@@ -129,8 +129,8 @@
file.inValidData.push({
rowIndex: rowIndex + 1,
columnIndex: columnIndex,
- message: _isFunction(valueConfig.validateError)
- ? valueConfig.validateError(valueConfig.name, rowIndex + 1, columnIndex)
+ message: _isFunction(valueConfig.dependentValidateError)
+ ? valueConfig.dependentValidateError(valueConfig.name, rowIndex + 1, columnIndex)
: String(valueConfig.name + ' not passed dependent validation in the ' + (rowIndex + 1) + ' row / ' + (columnIndex + 1) + ' column')
});
}
diff --git a/test.js b/test.js
index 7849dda..055696a 100644
--- a/test.js
+++ b/test.js
@@ -9,6 +9,10 @@ const validateError = (headerName, rowNumber, columnNumber) => (
`${headerName} is not valid in the ${rowNumber} row / ${columnNumber} column
`
)
+const dependentValidateError = (headerName, rowNumber, columnNumber) => (
+ `${headerName} is not valid. Country should be set to Ukraine. ${rowNumber} row / ${columnNumber} column
`
+)
+
const isEmailValid = (email) => {
const reqExp = /[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$/
return reqExp.test(email)
@@ -29,7 +33,7 @@ const CSVConfig = {
{ name: 'Email', inputName: 'email', required: true, requiredError, unique: true, uniqueError, validate: isEmailValid, validateError },
{ name: 'Password', inputName: 'password', required: true, requiredError, validate: isPasswordValid, validateError },
{ name: 'Roles', inputName: 'roles', required: true, requiredError, isArray: true },
- { name: 'Country', inputName: 'country', optional: true, dependentValidate: isRoleForCountryValid }
+ { name: 'Country', inputName: 'country', optional: true, dependentValidate: isRoleForCountryValid, dependentValidateError }
]
}
@@ -103,6 +107,9 @@ test('should return invalid messages with data', async t => {
t.is(csvData.inValidData.length, 5);
t.is(csvData.data.length, 2);
+ t.is(csvData.inValidData[3].message,
+ 'Country is not valid. Country should be set to Ukraine. 3 row / 6 column
'
+ );
});
test('should return data, the file is valid', async t => {
diff --git a/yarn.lock b/yarn.lock
index 19c793c..fb3a602 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -139,10 +139,10 @@
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301"
integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==
-"@types/papaparse@^5.3.2":
- version "5.3.2"
- resolved "https://registry.yarnpkg.com/@types/papaparse/-/papaparse-5.3.2.tgz#6ccace6eac8ddb03a6fd06883b84dd6c6561f69f"
- integrity sha512-BNbCHJkTE4RwmAFkCxEalET4mDvGr/1ld7ZtQ4i/laWI/iiVt+GL07stdvufle4KfywyvloqqpIiJscXNCrKxA==
+"@types/papaparse@^5.3.15":
+ version "5.3.15"
+ resolved "https://registry.yarnpkg.com/@types/papaparse/-/papaparse-5.3.15.tgz#7cafa16757a1d121422deefbb10b6310b224ecc4"
+ integrity sha512-JHe6vF6x/8Z85nCX4yFdDslN11d+1pr12E526X8WAfhadOeaOTx5AuIkvDKIBopfvlzpzkdMx4YyvSKCM9oqtw==
dependencies:
"@types/node" "*"
@@ -3312,10 +3312,10 @@ package-json@^4.0.0:
registry-url "^3.0.3"
semver "^5.1.0"
-papaparse@^5.3.2:
- version "5.3.2"
- resolved "https://registry.yarnpkg.com/papaparse/-/papaparse-5.3.2.tgz#d1abed498a0ee299f103130a6109720404fbd467"
- integrity sha512-6dNZu0Ki+gyV0eBsFKJhYr+MdQYAzFUGlBMNj3GNrmHxmz1lfRa24CjFObPXtjcetlOv5Ad299MhIK0znp3afw==
+papaparse@^5.4.1:
+ version "5.4.1"
+ resolved "https://registry.yarnpkg.com/papaparse/-/papaparse-5.4.1.tgz#f45c0f871853578bd3a30f92d96fdcfb6ebea127"
+ integrity sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw==
parse-glob@^3.0.4:
version "3.0.4"