import isEmpty from 'lodash/isEmpty'; import uniq from 'lodash/uniq'; import { get } from '@shell/utils/object'; import { camelToTitle } from '@shell/utils/string'; import { validateKubernetesName } from './kubernetes-name'; export function displayKeyFor(type, key, getters) { const intlPrefix = `model.${ type }.${ key }`; const intlPrefixLabel = `${ intlPrefix }.label`; if ( getters['i18n/exists'](`${ intlPrefix }.label`) ) { return getters['i18n/t'](intlPrefixLabel); } if ( getters['i18n/exists'](intlPrefix) ) { return getters['i18n/t'](intlPrefix); } if ( key.match(/.Id$/) ) { return camelToTitle(key.replace(/Id$/, '')); } return camelToTitle(key); } export function validateLength(val, field, displayKey, getters, errors = []) { const { nullable, required, type = '', minLength, maxLength, min: fieldMin, max: fieldMax, } = field; const len = val ? get(val, 'length') : 0; if ( !nullable && required) { if ((typeof val === 'object' && isEmpty(val)) || (!val && val !== 0)) { errors.push(getters['i18n/t']('validation.required', { key: displayKey })); return errors; } } if ( val === null ) { return errors; } // i18n-uses validation.*.exactly, validation.*.between, validation.*.min, validation.*.max const lengthKey = (type.indexOf('array[') === 0 ? 'arrayLength' : 'stringLength'); // String and array length: let min = minLength; let max = maxLength; if ( min && max ) { if ( (len < min) || (len > max) ) { if ( min === max ) { errors.push(getters['i18n/t'](`validation.${ lengthKey }.exactly`, { key: displayKey, count: min })); } else { errors.push(getters['i18n/t'](`validation.${ lengthKey }.between`, { key: displayKey, min, max })); } } } else if ( min && (len < min) ) { errors.push(getters['i18n/t'](`validation.${ lengthKey }.min`, { key: displayKey, count: min })); } else if ( max && (len > max) ) { errors.push(getters['i18n/t'](`validation.${ lengthKey }.max`, { key: displayKey, count: max })); } // Number min/max min = fieldMin; max = fieldMax; if ( val !== null && min && max ) { if ( (val < min) || (val > max) ) { if ( min === max ) { errors.push(getters['i18n/t']('validation.number.exactly', { key: displayKey, val: max })); } else { errors.push(getters['i18n/t']('validation.number.between', { key: displayKey, min, max })); } } } else if ( min && (val < min) ) { errors.push(getters['i18n/t']('validation.number.min', { key: displayKey, val: min })); } else if ( max && (val > max) ) { errors.push(getters['i18n/t']('validation.number.max', { key: displayKey, val: max })); } return errors; } export function validateChars(val, field, displayKey, getters, errors = []) { const { validChars, invalidChars } = field; const test = []; if ( validChars ) { test.push(`[^${ field.validChars }]`); } if ( invalidChars ) { test.push(`[${ field.invalidChars }]`); } if ( test.length ) { const regex = new RegExp(`(${ test.join('|') })`, 'g'); let match = val.match(regex); if ( match ) { match = uniq(match).map((chr) => { if ( chr === ' ' ) { return '[space]'; } else { return chr; } }); errors.push(getters['i18n/t']('validation.chars', { key: displayKey, count: match.length, chars: match.join(' ') })); } } return errors; } export function validateHostname(val, displayKey, getters, opts, errors = []) { opts = opts || {}; const { max = 253, restricted = false, } = opts; // Hostname can not start with a dot if (val.slice(0, 1) === '.') { errors.push(getters['i18n/t']('validation.dns.hostname.startDot', { key: displayKey })); } // Hostname can not end with a dot in restricted mode if ( restricted && val.length > 1 && val.slice(-1) === '.' ) { errors.push(getters['i18n/t']('validation.dns.hostname.endDot', { key: displayKey })); } // Hostname can not be empty string if (val.length === 0) { errors.push(getters['i18n/t']('validation.dns.hostname.empty', { key: displayKey })); } // Total length of the hostname can be at most 253 characters // (255 minus one for null-termination, and one for the trailing dot of a real FQDN) if (val.length > max) { errors.push(getters['i18n/t']('validation.dns.hostname.tooLong', { key: displayKey, max })); } // Split the hostname with the dot and validate the element as label const labels = val.split(/\./); let label; for ( let i = 0 ; i < labels.length ; i++ ) { label = labels[i]; // Already checked if Hostname starts with a dot if ( i === 0 && label === '' ) { continue; } // Hostname can end with a dot (this makes it an explicitly fully qualified domain name) // so the last element of the labels can be empty string. if (i === labels.length - 1 && label === '') { continue; } validateDnsLabel(label, displayKey, getters, { forHostname: true }, errors); } return errors; } export function validateDnsLabel(label, displayKey, getters, opts, errors = []) { opts = opts || {}; const { forHostname = false, errorKey = (forHostname ? 'hostname' : 'label'), ianaServiceName = false, restricted = false, } = opts; validateKubernetesName(label, displayKey, getters, opts, errors); // Restricted labels cannot begin with a number if ( restricted && label.slice(0, 1).match(/[0-9]/) ) { errors.push(getters['i18n/t'](`validation.dns.${ errorKey }.startNumber`, { key: displayKey })); } // Label cannot contain two consecutive hyphens at the 3rd & 4th characters, unless an IDN string // If is of type ianaServiceName can not contain two consecutive hyphens in any position if ( ( label.substr(2, 2) === '--' && label.substr(0, 2) !== 'xn' ) || ( ianaServiceName && label.includes('--') ) ) { errors.push(getters['i18n/t'](`validation.dns.doubleHyphen`, { key: displayKey })); } return errors; } export function validateDnsLikeTypes(val, type, displayKey, getters, opts, errors = []) { switch (type) { case 'dnsLabel': validateDnsLabel(val, displayKey, getters, { restricted: false }, errors); break; case 'dnsLabelRestricted': validateDnsLabel(val, displayKey, getters, { restricted: true }, errors); break; case 'hostname': validateHostname(val, displayKey, getters, { restricted: false }, errors); break; default: break; } return errors; } export function validateBoolean(val, field, displayKey, getters, errors = []) { const { required } = field; if (required && !val && val !== false) { errors.push(getters['i18n/t']('validation.required', { key: displayKey })); return; } if (typeof val !== 'boolean' && !!val) { errors.push(getters['i18n/t']('validation.boolean', { key: displayKey })); } }