Skip to content

Commit

Permalink
Merge pull request #316 from kellnerd/entity-validators
Browse files Browse the repository at this point in the history
Unify entity validators
  • Loading branch information
MonkeyDo authored Jun 26, 2024
2 parents e614e50 + f3ec5ea commit 7d1df5a
Show file tree
Hide file tree
Showing 26 changed files with 3,906 additions and 1 deletion.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@
"knex": "^2.4.2",
"lodash": "^4.17.21",
"moment": "^2.29.1",
"pg": "^8.6.0"
"pg": "^8.6.0",
"validator": "^13.7.0"
},
"devDependencies": {
"@babel/cli": "^7.14.3",
Expand Down
27 changes: 27 additions & 0 deletions src/func/author.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (C) 2019 Kartik Pandey
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

export function labelsForAuthor(isGroup: boolean) {
return {
beginAreaLabel: isGroup ? 'Place founded' : 'Place of birth',
beginDateLabel: isGroup ? 'Date founded' : 'Date of birth',
endAreaLabel: isGroup ? 'Place of dissolution' : 'Place of death',
endDateLabel: isGroup ? 'Date of dissolution' : 'Date of death',
endedLabel: isGroup ? 'Dissolved?' : 'Died?'
};
}
70 changes: 70 additions & 0 deletions src/func/date.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright (C) 2019 Nicolas Pelletier
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

import _ from 'lodash';


export interface DateObject {
day: string | null;
month: string | null;
year: string | null;
}

/**
* Parse an ISO 8601-2004 string and return an object with separate day, month and year, if they exist.
* If any of the values don't exist, the default is an empty string.
* @function ISODateStringToObject
* @param {string} value - relationshipId number for initaial relationship
* @returns {object} a {day, month, year} object
*/
export function ISODateStringToObject(value: string | DateObject): DateObject {
if (!_.isString(value)) {
if (_.isPlainObject(value) && _.has(value, 'year')) {
return value;
}
return {day: '', month: '', year: ''};
}
const date = value ? value.split('-') : [];
// A leading minus sign denotes a BC date
// This creates an empty first array item that needs to be removed,
// and requires us to add the negative sign back for the year
if (date.length && date[0] === '') {
date.shift();
date[0] = (-parseInt(date[0], 10)).toString();
}
return {
day: date.length > 2 ? date[2] : '',
month: date.length > 1 ? date[1] : '',
year: date.length > 0 ? date[0] : ''
};
}

/**
* Determines wether a given date is empty or null, meaning no year month or day has been specified.
* Accepts a {day, month, year} object or an ISO 8601-2004 string (±YYYYYY-MM-DD)
* @function isNullDate
* @param {object|string} date - a {day, month, year} object or ISO 8601-2004 string (±YYYYYY-MM-DD)
* @returns {boolean} true if the date is empty/null
*/
export function isNullDate(date: string | DateObject): boolean {
const dateObject = ISODateStringToObject(date);
const isNullYear = _.isNil(dateObject.year) || dateObject.year === '';
const isNullMonth = _.isNil(dateObject.month) || dateObject.month === '';
const isNullDay = _.isNil(dateObject.day) || dateObject.day === '';
return isNullYear && isNullMonth && isNullDay;
}
9 changes: 9 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/

import type Bookshelf from '@metabrainz/bookshelf';
import {Iterable} from 'immutable';
import type {Transaction} from './func/types';
import _ from 'lodash';
import {diff} from 'deep-diff';
Expand Down Expand Up @@ -280,3 +281,11 @@ export async function promiseProps<T>(promiseObj: Record<string, T>): Promise<Re
);
return Object.fromEntries(resolvedKeyValuePairs);
}

export function isIterable<K, V>(testVal: any): testVal is Iterable<K, V> {
return Iterable.isIterable(testVal);
}

export function convertMapToObject(value) {
return isIterable(value) ? value.toJS() : value;
}
117 changes: 117 additions & 0 deletions src/validators/author.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* Copyright (C) 2017 Ben Ockmore
* 2024 David Kellner
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/


import {
type AreaStub,
validateAliases,
validateIdentifiers,
validateNameSection,
validateSubmissionSection
} from './common';
import {ValidationError, dateIsBefore, get, validateDate, validatePositiveInteger} from './base';

import type {IdentifierTypeWithIdT} from '../types/identifiers';
import _ from 'lodash';
import {labelsForAuthor} from '../func/author';


export function validateAuthorSectionBeginArea(value: any): void {
if (!value) {
return;
}

validatePositiveInteger(get(value, 'id', null), 'authorSection.beginArea.id', true);
}

export function validateAuthorSectionBeginDate(value: any) {
validateDate(value, 'authorSection.beginDate');
}

export function validateAuthorSectionEndArea(value: any): void {
if (!value) {
return;
}

validatePositiveInteger(get(value, 'id', null), 'authorSection.endArea.id', true);
}

export function validateAuthorSectionEndDate(
beginValue: any, endValue: any, authorType?: string
): void {
validateDate(endValue, 'authorSection.endDate');
try {
validateDate(beginValue, 'beginDate');
}
catch (error) {
// Ignore invalid begin date during end date validation.
// TODO: It probably makes more sense to drop these silly test cases?
return;
}

const isGroup = authorType === 'Group';
const {beginDateLabel, endDateLabel} = labelsForAuthor(isGroup);
if (!dateIsBefore(beginValue, endValue)) {
throw new ValidationError(`${endDateLabel} must be greater than ${beginDateLabel}`);
}
}

export function validateAuthorSectionEnded(value: any): void {
if (!(_.isNull(value) || _.isBoolean(value))) {
throw new ValidationError('Value has to be a boolean or `null`', 'authorSection.ended');
}
}

export function validateAuthorSectionType(value: any): void {
validatePositiveInteger(value, 'authorSection.type');
}

export function validateAuthorSectionGender(value: any): void {
validatePositiveInteger(value, 'authorSection.gender');
}

export function validateAuthorSection(data: any): void {
validateAuthorSectionBeginArea(get(data, 'beginArea', null));
validateAuthorSectionBeginDate(get(data, 'beginDate', ''));
validateAuthorSectionEndArea(get(data, 'endArea', null));
validateAuthorSectionEndDate(get(data, 'beginDate', ''), get(data, 'endDate', ''));
validateAuthorSectionEnded(get(data, 'ended', null));
validateAuthorSectionType(get(data, 'gender', null));
validateAuthorSectionType(get(data, 'type', null));
}

export function validateAuthor(
formData: any, identifierTypes?: Array<IdentifierTypeWithIdT> | null | undefined
): void {
validateAliases(get(formData, 'aliasEditor', {}));
validateIdentifiers(get(formData, 'identifierEditor', {}), identifierTypes);
validateNameSection(get(formData, 'nameSection', {}));
validateAuthorSection(get(formData, 'authorSection', {}));
validateSubmissionSection(get(formData, 'submissionSection', {}));
}

export type AuthorSection = Partial<{
beginArea: AreaStub;
beginDate: string;
endArea: AreaStub;
endDate: string;
ended: boolean;
gender: number;
type: number;
}>;
Loading

0 comments on commit 7d1df5a

Please sign in to comment.