huridocs/uwazi

View on GitHub
app/api/entities/validation/metadataValidators.js

Summary

Maintainability
A
0 mins
Test Coverage
A
98%
import { propertyTypes } from 'shared/propertyTypes';

const validateSingleWrappedValue = validationFn => value => {
  if (value.length !== 1) {
    return !value.length;
  }

  if (value[0].value === null) {
    return true;
  }

  const [{ value: pureValue }] = value;
  return validationFn(pureValue);
};

const isNumber = value => typeof value === 'number';

const isUndefined = value => value === undefined;

const isString = value => typeof value === 'string';

const isObject = value => value !== null && typeof value === 'object';

const isNull = value => value === null;

const isEmpty = value =>
  isNull(value) || isUndefined(value) || !value.length || !value.some(v => v.value);

const isNonArrayObject = value => isObject(value) && !Array.isArray(value);

const validateDateProperty = value => isNumber(value);

const isValidDateRange = value => {
  if (!isNonArrayObject(value)) {
    return false;
  }

  if (validateDateProperty(value.from) && validateDateProperty(value.to)) {
    return value.from <= value.to;
  }

  if (isString(value.from) || isString(value.to)) {
    return false;
  }

  return true;
};

const isValidSelect = value => isString(value);

const isValidGeolocation = value => isNumber(value.lat) && isNumber(value.lon);

const validateRequiredProperty = (property, value) => {
  if (property.required) {
    if (property.type === 'numeric') {
      return (value[0] && value[0].value === 0) || !isEmpty(value);
    }

    return !isEmpty(value);
  }
  return true;
};

const isValidLinkField = value =>
  isString(value.label) && isString(value.url) && ((value.label && value.url) || !value.label);

const validateNumericProperty = value =>
  isNumber(value) || value === '' || (isString(value) && `${parseFloat(value, 10)}` === value);

const validateMultiDateProperty = value =>
  Array.isArray(value) && value.every(item => isNumber(item.value) || isNull(item.value));

const validateMultiDateRangeProperty = value =>
  Array.isArray(value) && value.every(item => isValidDateRange(item.value));

const validateGeolocationProperty = value =>
  Array.isArray(value) && value.every(item => isValidGeolocation(item.value));

const validateMultiSelectProperty = value =>
  Array.isArray(value) && value.every(item => isValidSelect(item.value) && item.value);

const validateLuceneBytesLimit = value => {
  const LUCENE_BYTES_LIMIT = 32766;
  const bytes = Buffer.from(JSON.stringify(value));
  return bytes.length < LUCENE_BYTES_LIMIT;
};

export const customErrorMessages = {
  required: 'property is required',
  read_only: 'property is read-only',
  length_exceeded: 'maximum field length exceeded',
  property_not_allowed: 'property is not configured on the template, it is not allowed',
  relationship_wrong_foreign_id: 'related entities do not exist or belong to another template',
  dictionary_wrong_foreing_id: 'related dictionary value/s does not exists',
  relationship_values_should_match:
    'relationships with the same configuration should have the same values',
  [propertyTypes.date]: 'should be number',
  [propertyTypes.multidate]: 'should be an array of numbers',
  [propertyTypes.daterange]:
    'should be a "{ to: number, from: number }" object, "to" should be greater than "from"',
  [propertyTypes.multidaterange]:
    'should be a "[ { to: number, from: number } ]" collection, "to" should be greater than "from"',
  [propertyTypes.text]: 'should be a string',
  [propertyTypes.markdown]: 'should be a string',
  [propertyTypes.media]: 'should be a string',
  [propertyTypes.image]: 'should be a string',
  [propertyTypes.select]: 'should be a "[ { value: string } ]" with a single object',
  [propertyTypes.multiselect]: 'should be an array of non empty strings',
  [propertyTypes.relationship]: 'should be an array of non empty strings',
  [propertyTypes.newRelationship]: 'should be an array of non empty strings',
  [propertyTypes.numeric]: 'should be number',
  [propertyTypes.link]:
    'should be a "{ label: string, url: string }" object properties can not be blank',
  [propertyTypes.geolocation]:
    'should be a "[ { lat: number, lon: number, label: string } ]" collection, lat and lon are required',
};

export const validators = {
  [propertyTypes.date]: validateSingleWrappedValue(validateDateProperty),
  [propertyTypes.multidate]: validateMultiDateProperty,
  [propertyTypes.daterange]: validateSingleWrappedValue(isValidDateRange),
  [propertyTypes.multidaterange]: validateMultiDateRangeProperty,
  [propertyTypes.text]: validateSingleWrappedValue(isString),
  [propertyTypes.markdown]: validateSingleWrappedValue(isString),
  [propertyTypes.media]: validateSingleWrappedValue(isString),
  [propertyTypes.image]: validateSingleWrappedValue(isString),
  [propertyTypes.select]: validateSingleWrappedValue(isValidSelect),
  [propertyTypes.numeric]: validateSingleWrappedValue(validateNumericProperty),
  [propertyTypes.multiselect]: validateMultiSelectProperty,
  [propertyTypes.relationship]: validateMultiSelectProperty,
  [propertyTypes.newRelationship]: validateMultiSelectProperty,
  [propertyTypes.link]: validateSingleWrappedValue(isValidLinkField),
  [propertyTypes.geolocation]: validateGeolocationProperty,
  validateRequiredProperty,
  validateLuceneBytesLimit,
};