Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

No fail validation #124

Merged
merged 11 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
);
```
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
23 changes: 22 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -21,6 +26,7 @@ export {default as MarcRecordError} from './error';

const validationOptionsDefaults = {
strict: false,
noFailValidation: false,
fields: true,
subfields: true,
subfieldValues: true,
Expand Down Expand Up @@ -58,16 +64,31 @@ 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;
}

this.leader = '';
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));
}
Expand Down
5 changes: 5 additions & 0 deletions src/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,11 @@ describe('index', () => {
return MarcRecord.setValidationOptions(args);
}

//-------------------------------------------------------------------------
if (name === 'getValidationErrors') {
return record.getValidationErrors();
}

//-------------------------------------------------------------------------
if (name === 'MarcRecord') {
const {leader, fields, validationOptions} = args ?? {};
Expand Down
18 changes: 14 additions & 4 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 [];
}
2 changes: 1 addition & 1 deletion test-fixtures/index/constructor/03/metadata.json
Original file line number Diff line number Diff line change
@@ -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": [
Expand Down
2 changes: 1 addition & 1 deletion test-fixtures/index/constructor/04/metadata.json
Original file line number Diff line number Diff line change
@@ -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": [
Expand Down
10 changes: 7 additions & 3 deletions test-fixtures/index/constructor/05/metadata.json
Original file line number Diff line number Diff line change
@@ -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": []
},
Expand Down
2 changes: 1 addition & 1 deletion test-fixtures/index/constructor/06/metadata.json
Original file line number Diff line number Diff line change
@@ -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": [
Expand Down
2 changes: 1 addition & 1 deletion test-fixtures/index/constructor/07/metadata.json
Original file line number Diff line number Diff line change
@@ -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": [
Expand Down
19 changes: 19 additions & 0 deletions test-fixtures/index/constructor/10/metadata.json
Original file line number Diff line number Diff line change
@@ -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
}
29 changes: 29 additions & 0 deletions test-fixtures/index/constructor/11/metadata.json
Original file line number Diff line number Diff line change
@@ -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
}
14 changes: 14 additions & 0 deletions test-fixtures/index/validationErrors/01/metadata.json
Original file line number Diff line number Diff line change
@@ -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
}
17 changes: 17 additions & 0 deletions test-fixtures/index/validationErrors/02/metadata.json
Original file line number Diff line number Diff line change
@@ -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
}
20 changes: 20 additions & 0 deletions test-fixtures/index/validationErrors/03/metadata.json
Original file line number Diff line number Diff line change
@@ -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
}
17 changes: 17 additions & 0 deletions test-fixtures/index/validationErrors/04/metadata.json
Original file line number Diff line number Diff line change
@@ -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
}
1 change: 1 addition & 0 deletions test-fixtures/index/validationOptions/01/metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"leader": false,
"noAdditionalProperties": false,
"noControlCharacters": false,
"noFailValidation": false,
"subfields": true,
"subfieldValues": true,
"strict": false
Expand Down
1 change: 1 addition & 0 deletions test-fixtures/index/validationOptions/02/metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"leader": false,
"noAdditionalProperties": false,
"noControlCharacters": false,
"noFailValidation": false,
"subfields": true,
"subfieldValues": true,
"strict": false
Expand Down