diff --git a/README.md b/README.md index 6b656c6..8eec8b0 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,16 @@ When constructing or modifying the record, validation checks are run. You may ne validationOption `strict: true` sets all the other validationOptions as true regardless of if they are defined validationOption `strict: false` sets other validationOptions as they are defined or as default +**noFailValidation** + +validationOption `noFailValidation: false` (default) throws an error when the record fails validation checks +validationOption `noFailValidation: true` saves errors in validationErrors instead of throwing an error when the record fails validation checks + +```js +// return validationErrors +record.getValidationErrors(); +``` + **Global validation options:** ```js @@ -60,7 +70,8 @@ MarcRecord.setValidationOptions( noControlCharacters: false, // Do not allow ASCII control characters in field/subfield values noAdditionalProperties: false, // Do not allow additional properties in fields - strict: false // If true, set all validationOptions to true + strict: false, // If true, set all validationOptions to true + noFailValidation: false // If true, do not error if validation fails, save errors in validationErrors instead } ); ``` @@ -79,7 +90,6 @@ You can set all global validation options to true with validationOption strict: MarcRecord.setValidationOptions({strict: true}); ``` - **Record specific validation options** can be given when constructing: ```js diff --git a/package-lock.json b/package-lock.json index 5dd9294..e144ecc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@natlibfi/marc-record", - "version": "9.0.2", + "version": "9.1.0-alpha.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@natlibfi/marc-record", - "version": "9.0.2", + "version": "9.1.0-alpha.4", "license": "MIT", "dependencies": { "debug": "^4.3.7", diff --git a/package.json b/package.json index 79e558c..948f09d 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "url": "git@github.com:natlibfi/marc-record-js.git" }, "license": "MIT", - "version": "9.0.2", + "version": "9.1.0-alpha.4", "main": "./dist/index.js", "engines": { "node": ">=18" diff --git a/src/index.js b/src/index.js index 2c310d3..a9ed5c5 100644 --- a/src/index.js +++ b/src/index.js @@ -4,11 +4,16 @@ import {fieldOrderComparator} from './marcFieldSort'; import {clone, validateRecord, validateField} from './utils'; import MarcRecordError from './error'; export {default as MarcRecordError} from './error'; +import createDebugLogger from 'debug'; +const debug = createDebugLogger('@natlibfi/marc-record'); +//const debugData = debug.extend('data'); +const debugDev = debug.extend('dev'); // Default setting for validationOptions: // These default validationOptions are (mostly) backwards compatible with marc-record-js < 7.3.0 // // strict: false // All validationOptions below are set to true +// noFailValidation: false // Do not error if validation fails, return validationResults instead // // fields: true, // Do not allow record without fields // subfields: true, // Do not allow empty subfields @@ -21,6 +26,7 @@ export {default as MarcRecordError} from './error'; const validationOptionsDefaults = { strict: false, + noFailValidation: false, fields: true, subfields: true, subfieldValues: true, @@ -58,9 +64,16 @@ export class MarcRecord { field.ind2 = field.ind2 || ' '; }); - validateRecord(recordClone, {...globalValidationOptions, ...this._validationOptions}); this.leader = recordClone.leader; this.fields = recordClone.fields; + + this._validationErrors = validateRecord(recordClone, {...globalValidationOptions, ...this._validationOptions}); + if (!this._validationOptions.noFailValidation) { + // eslint-disable-next-line functional/immutable-data + delete this._validationErrors; + return; + } + debugDev(`${JSON.stringify(this)}`); return; } @@ -68,6 +81,14 @@ export class MarcRecord { this.fields = []; } + getValidationErrors() { + debugDev(`getting validationErrors: ${this._validationErrors} <-`); + if (!this._validationOptions.noFailValidation) { + return []; + } + return this._validationErrors || []; + } + get(query) { return this.fields.filter(field => field.tag.match(query)); } diff --git a/src/index.spec.js b/src/index.spec.js index 801d412..cbd3498 100644 --- a/src/index.spec.js +++ b/src/index.spec.js @@ -316,6 +316,11 @@ describe('index', () => { return MarcRecord.setValidationOptions(args); } + //------------------------------------------------------------------------- + if (name === 'getValidationErrors') { + return record.getValidationErrors(); + } + //------------------------------------------------------------------------- if (name === 'MarcRecord') { const {leader, fields, validationOptions} = args ?? {}; diff --git a/src/utils.js b/src/utils.js index 865eb42..22dae4c 100644 --- a/src/utils.js +++ b/src/utils.js @@ -13,23 +13,33 @@ const debugData = debug.extend('data'); export function validateRecord(record, options = {}) { + const {noFailValidation} = options; const validationResults = validate(record, createSchema(options), {nestedErrors: false}); - debugData(JSON.stringify(record)); + //debugData(JSON.stringify(record)); //debugDev(inspect(validationResults), {depth: 3}); //debugDev(inspect(validationResults.errors)); + debugData(validationResults.errors.toString()); + if (noFailValidation) { + return validationResults.errors.map(valError => valError.toString()); + } if (validationResults.errors.length > 0) { // eslint-disable-line functional/no-conditional-statements - debugData(validationResults.toString()); throw new MarcRecordError('Record is invalid', validationResults); } + return []; } export function validateField(field, options = {}) { + const {noFailValidation} = options; const validationResults = validate(field, createSchema(options).properties.fields.items, {nestedErrors: false}); - debugData(JSON.stringify(field)); + //debugData(JSON.stringify(field)); //debugDev(inspect(validationResults)); //debugDev(inspect(validationResults.errors)); + debugData(validationResults.errors.toString()); + if (noFailValidation) { + return validationResults.errors.map(valError => valError.toString()); + } if (validationResults.errors.length > 0) { // eslint-disable-line functional/no-conditional-statements - debugData(validationResults.toString()); throw new MarcRecordError(`Field is invalid: ${JSON.stringify(field)}`, validationResults); } + return []; } diff --git a/test-fixtures/index/constructor/03/metadata.json b/test-fixtures/index/constructor/03/metadata.json index 2051107..9d8768b 100644 --- a/test-fixtures/index/constructor/03/metadata.json +++ b/test-fixtures/index/constructor/03/metadata.json @@ -1,5 +1,5 @@ { - "description": "Should fail to create record without subfields", + "description": "Should fail to create record that has a field without subfields or value", "skip": false, "noinput": true, "operations": [ diff --git a/test-fixtures/index/constructor/04/metadata.json b/test-fixtures/index/constructor/04/metadata.json index 586cbb3..f3cee4c 100644 --- a/test-fixtures/index/constructor/04/metadata.json +++ b/test-fixtures/index/constructor/04/metadata.json @@ -1,5 +1,5 @@ { - "description": "Should fail to create record without subfields values", + "description": "Should fail to create record that has a (data)field without subfields values", "skip": false, "noinput": true, "operations": [ diff --git a/test-fixtures/index/constructor/05/metadata.json b/test-fixtures/index/constructor/05/metadata.json index a875a1d..291cfec 100644 --- a/test-fixtures/index/constructor/05/metadata.json +++ b/test-fixtures/index/constructor/05/metadata.json @@ -1,16 +1,20 @@ { - "description": "Should create MarcRecord without subfields", + "description": "Should create MarcRecord that has no fields, but return validationErrors (validationOptions {\"noFailValidation\": true, \"fields\": true} )", "skip": false, + "only": false, "noinput": true, "operations": [ { "name": "MarcRecord", "args": { "leader": "02848ccm a22005894i 4500", - "validationOptions": {"fields": false} + "validationOptions": {"noFailValidation": true, "fields": true} } } ], "returns": { - "_validationOptions": {"fields": false}, + "_validationErrors": [ + "instance.fields does not meet minimum length of 1" + ], + "_validationOptions": {"fields": true, "noFailValidation": true}, "leader": "02848ccm a22005894i 4500", "fields": [] }, diff --git a/test-fixtures/index/constructor/06/metadata.json b/test-fixtures/index/constructor/06/metadata.json index cd49b12..c1c1395 100644 --- a/test-fixtures/index/constructor/06/metadata.json +++ b/test-fixtures/index/constructor/06/metadata.json @@ -1,5 +1,5 @@ { - "description": "Should create MarcRecord without subfields", + "description": "Should create MarcRecord that has a (data)field without subfields validationOptions: \"subfields\": false", "skip": false, "noinput": true, "operations": [ diff --git a/test-fixtures/index/constructor/07/metadata.json b/test-fixtures/index/constructor/07/metadata.json index 08070bc..7fd00a3 100644 --- a/test-fixtures/index/constructor/07/metadata.json +++ b/test-fixtures/index/constructor/07/metadata.json @@ -1,5 +1,5 @@ { - "description": "Should create MarcRecord without subfield values", + "description": "Should create MarcRecord that has a (data)field without subfield values, validationOption: \"subfieldValues\": false", "skip": false, "noinput": true, "operations": [ diff --git a/test-fixtures/index/constructor/10/metadata.json b/test-fixtures/index/constructor/10/metadata.json new file mode 100644 index 0000000..bc62a93 --- /dev/null +++ b/test-fixtures/index/constructor/10/metadata.json @@ -0,0 +1,19 @@ +{ + "description": "Should create MarcRecord that has no fields (validationOptions {\"fields\": false} )", + "skip": false, + "only": false, + "noinput": true, + "operations": [ + { "name": "MarcRecord", "args": { + "leader": "02848ccm a22005894i 4500", + "validationOptions": {"fields": false} + } + } + ], + "returns": { + "_validationOptions": {"fields": false}, + "leader": "02848ccm a22005894i 4500", + "fields": [] + }, + "immutable": true +} diff --git a/test-fixtures/index/constructor/11/metadata.json b/test-fixtures/index/constructor/11/metadata.json new file mode 100644 index 0000000..deb8357 --- /dev/null +++ b/test-fixtures/index/constructor/11/metadata.json @@ -0,0 +1,29 @@ +{ + "description": "Should create MarcRecord that has no fields, but return validationErrors (validationOptions {\"noFailValidation\": true, \"fields\": true} )", + "skip": false, + "only": false, + "noinput": true, + "operations": [ + { "name": "MarcRecord", "args": { + "leader": "02848ccm a22005894i 4500", + "fields": [ + {"tag": "245", "ind1": " ", "ind2": " ", "subfields": []}, + {"tag": "500", "ind1": " ", "ind2": " ", "subfields": []} + ], + "validationOptions": {"noFailValidation": true, "fields": true} + } + } + ], + "returns": { + "_validationErrors": [ + "instance.fields[0] is not any of [subschema 0],[subschema 1]", + "instance.fields[1] is not any of [subschema 0],[subschema 1]" + ], + "_validationOptions": {"fields": true, "noFailValidation": true}, + "leader": "02848ccm a22005894i 4500", + "fields": [ + {"tag": "245", "ind1": " ", "ind2": " ", "subfields": []}, + {"tag": "500", "ind1": " ", "ind2": " ", "subfields": []}] + }, + "immutable": true +} diff --git a/test-fixtures/index/validationErrors/01/metadata.json b/test-fixtures/index/validationErrors/01/metadata.json new file mode 100644 index 0000000..8a6e12e --- /dev/null +++ b/test-fixtures/index/validationErrors/01/metadata.json @@ -0,0 +1,14 @@ +{ + "description": "Should return validationErrors", + "skip": false, + "only": false, + "input": {"leader": "02848ccm a22005894i 4500"}, + "validationOptions": {"fields": true, "noFailValidation": true}, + "operations": [ + { "name": "getValidationErrors"} + ], + "returns": [ + "instance.fields does not meet minimum length of 1" + ], + "immutable": true +} diff --git a/test-fixtures/index/validationErrors/02/metadata.json b/test-fixtures/index/validationErrors/02/metadata.json new file mode 100644 index 0000000..df15b41 --- /dev/null +++ b/test-fixtures/index/validationErrors/02/metadata.json @@ -0,0 +1,17 @@ +{ + "description": "Should return several validationErrors", + "skip": false, + "only": false, + "input": { + "leader": "02848ccm a22005894i 4500", + "fields": [ + {"tag": "245", "ind1": " ", "ind2": " ", "subfields": []}, + {"tag": "500", "ind1": " ", "ind2": " ", "subfields": []} + ]}, + "validationOptions": {"noFailValidation": true, "subfields": false}, + "operations": [ + { "name": "getValidationErrors"} + ], + "returns": [], + "immutable": true +} diff --git a/test-fixtures/index/validationErrors/03/metadata.json b/test-fixtures/index/validationErrors/03/metadata.json new file mode 100644 index 0000000..b2a231a --- /dev/null +++ b/test-fixtures/index/validationErrors/03/metadata.json @@ -0,0 +1,20 @@ +{ + "description": "Should return several validationErrors", + "skip": false, + "only": false, + "input": { + "leader": "02848ccm a22005894i 4500", + "fields": [ + {"tag": "245", "ind1": " ", "ind2": " ", "subfields": []}, + {"tag": "500", "ind1": " ", "ind2": " ", "subfields": []} + ]}, + "validationOptions": {"noFailValidation": true, "subfields": true}, + "operations": [ + { "name": "getValidationErrors"} + ], + "returns": [ + "instance.fields[0] is not any of [subschema 0],[subschema 1]", + "instance.fields[1] is not any of [subschema 0],[subschema 1]" + ], + "immutable": true +} diff --git a/test-fixtures/index/validationErrors/04/metadata.json b/test-fixtures/index/validationErrors/04/metadata.json new file mode 100644 index 0000000..42b3fc4 --- /dev/null +++ b/test-fixtures/index/validationErrors/04/metadata.json @@ -0,0 +1,17 @@ +{ + "description": "Should return empty array if validationErrors is undefined", + "skip": false, + "only": false, + "input": { + "leader": "02848ccm a22005894i 4500", + "fields": [ + {"tag": "245", "ind1": " ", "ind2": " ", "subfields": [{"code": "a", "value": "A"}]}, + {"tag": "500", "ind1": " ", "ind2": " ", "subfields": [{"code": "a", "value": "A"}]} + ]}, + "validationOptions": {"noFailValidation": false, "subfields": true}, + "operations": [ + { "name": "getValidationErrors"} + ], + "returns": [], + "immutable": true +} diff --git a/test-fixtures/index/validationOptions/01/metadata.json b/test-fixtures/index/validationOptions/01/metadata.json index 87aa3c5..2883e2e 100644 --- a/test-fixtures/index/validationOptions/01/metadata.json +++ b/test-fixtures/index/validationOptions/01/metadata.json @@ -14,6 +14,7 @@ "leader": false, "noAdditionalProperties": false, "noControlCharacters": false, + "noFailValidation": false, "subfields": true, "subfieldValues": true, "strict": false diff --git a/test-fixtures/index/validationOptions/02/metadata.json b/test-fixtures/index/validationOptions/02/metadata.json index 5c1274d..199b52e 100644 --- a/test-fixtures/index/validationOptions/02/metadata.json +++ b/test-fixtures/index/validationOptions/02/metadata.json @@ -15,6 +15,7 @@ "leader": false, "noAdditionalProperties": false, "noControlCharacters": false, + "noFailValidation": false, "subfields": true, "subfieldValues": true, "strict": false