Skip to content
Open
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
2 changes: 2 additions & 0 deletions src/lib/kit/constants/config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ import {
getArrayValidator,
getBooleanValidator,
getNumberValidator,
getNumberWithScaleValidator,
getObjectValidator,
getStringValidator,
} from '../validators';
Expand Down Expand Up @@ -200,6 +201,7 @@ export const dynamicConfig: DynamicFormConfig = {
validators: {
base: getStringValidator(),
number: getNumberValidator() as unknown as ValidatorType<string, StringSpec>,
number_with_scale: getNumberWithScaleValidator(),
},
},
};
Expand Down
2 changes: 2 additions & 0 deletions src/lib/kit/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
],
"label_error-min-number": "Value must be an integer no less than {{count}}",
"label_error-max-number": "Value must not be greater than {{count}}",
"label_error-min-number-with-scale": "Value must be no less than {{count}} {{scale}}",
"label_error-max-number-with-scale": "Value must not be greater than {{count}} {{scale}}",
"label_error-space-end": "Value must not end with a space",
"label_error-space-start": "Value must not start with a space",
"label_error-zero-start": "Value must not start with a zero",
Expand Down
2 changes: 2 additions & 0 deletions src/lib/kit/i18n/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
],
"label_error-min-number": "Значение должно быть числом не меньше {{count}}",
"label_error-max-number": "Значение должно быть числом не больше {{count}}",
"label_error-min-number-with-scale": "Значение должно быть не меньше {{count}} {{scale}}",
"label_error-max-number-with-scale": "Значение должно быть не больше {{count}} {{scale}}",
"label_error-space-end": "Значение не должно заканчиваться пробелом",
"label_error-space-start": "Значение не должно начинаться с пробела",
"label_error-zero-start": "Значение не должно начинаться с нуля",
Expand Down
114 changes: 114 additions & 0 deletions src/lib/kit/validators/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import type {StringSpec} from 'src/lib/core';

import {divide, multiply} from '../utils';

export const isInt = (value: string) => {
const regex = /^(?:[-+]?(?:0|[1-9]\d*))$/;

Expand All @@ -9,3 +13,113 @@ export const isFloat = (value: string) => {

return regex.test(value);
};

/**
* Scales value given in defaultFactor to scaleFactor
* @param value
* @param scaleFactor
* @param defaultFactor
* @returns value in scaleFactor
*/
const scaleValue = (value: number, scaleFactor: string, defaultFactor: string): string | null => {
if (defaultFactor === scaleFactor) {
return String(value);
}

const valueStr = String(value);

try {
if (BigInt(scaleFactor) > BigInt(defaultFactor)) {
const ratio = divide(scaleFactor, defaultFactor);
return ratio ? divide(valueStr, ratio, 6) : null;
}

const ratio = divide(defaultFactor, scaleFactor);
return ratio ? multiply(valueStr, ratio, 6) : null;
} catch {
return null;
}
};

/**
* Checks if the number is > 1
* @param numStr - number in a string form
* @returns true if the number is > 1, false otherwise
*/
const isReadable = (numStr: string | null): boolean => {
if (numStr === null) {
return false;
}

// Math.abs does not support BigInt
const intPart = numStr.split('.')[0].replace('-', '');

try {
return BigInt(intPart) >= BigInt(1);
} catch {
return false;
}
};

/**
* Returns the biggest readable scale for the given limit
* @param sizeParams - sizeParams of NumberWithScale
* @param value - value in defaultFactor scale, which is referenced to deduce appropriate scale
* @returns the biggest readable scale for the given limit
*/
const getMostAppropriateScale = (
sizeParams: NonNullable<StringSpec['viewSpec']['sizeParams']>,
value: number,
): string => {
const {defaultType, scale} = sizeParams;

const valueStr = String(value);

const defaultFactor = scale[defaultType].factor;
let bestType = defaultType;

Object.keys(scale).forEach((key) => {
if (BigInt(scale[key].factor) > BigInt(scale[bestType].factor)) {
const ratio = divide(scale[key].factor, defaultFactor);

if (!ratio) {
return;
}

if (!isReadable(divide(valueStr, ratio, 2))) {
return;
}

bestType = key;
}
});

return bestType;
};

export const getScaledLimit = (
spec: StringSpec,
limit: number,
): {count: string; scaleTitle: string} | undefined => {
const sizeParams = spec.viewSpec?.sizeParams;

if (!sizeParams) {
return undefined;
}

const mostAppropriateScale = getMostAppropriateScale(sizeParams, limit);
const scaleEntry = sizeParams.scale[mostAppropriateScale];
const defaultEntry = sizeParams.scale[sizeParams.defaultType];

if (!scaleEntry || !defaultEntry) {
return undefined;
}

const count = scaleValue(limit, scaleEntry.factor, defaultEntry.factor);

if (count === null) {
return undefined;
}

return {count, scaleTitle: scaleEntry.title};
};
6 changes: 6 additions & 0 deletions src/lib/kit/validators/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ const getErrorMessages = (): ErrorMessagesType => ({
maxNumber(count: number | bigint) {
return i18n('label_error-max-number', {count});
},
minNumberWithScale(count: number | bigint | string, scale: string) {
return i18n('label_error-min-number-with-scale', {count, scale});
},
maxNumberWithScale(count: number | bigint | string, scale: string) {
return i18n('label_error-max-number-with-scale', {count, scale});
},
SPACE_START: i18n('label_error-space-start'),
SPACE_END: i18n('label_error-space-end'),
DOT_END: i18n('label_error-dot-end'),
Expand Down
2 changes: 2 additions & 0 deletions src/lib/kit/validators/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export interface ErrorMessagesType {
maxLengthArr: (count: number | bigint) => string;
minNumber: (count: number | bigint) => string;
maxNumber: (count: number | bigint) => string;
minNumberWithScale: (count: number | bigint | string, scale: string) => string;
maxNumberWithScale: (count: number | bigint | string, scale: string) => string;
SPACE_START: string;
SPACE_END: string;
DOT_END: string;
Expand Down
115 changes: 114 additions & 1 deletion src/lib/kit/validators/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import type {
} from '../../core';
import {ErrorMessages} from '../validators';

import {isFloat, isInt} from './helpers';
import {getScaledLimit, isFloat, isInt} from './helpers';
import type {ErrorMessagesType} from './types';

interface CommonValidatorParams {
Expand Down Expand Up @@ -186,6 +186,119 @@ export const getNumberValidator = (params: GetNumberValidatorParams = {}) => {
};
};

export interface GetNumberWithScaleValidatorParams extends GetNumberValidatorParams {}

interface NumberWithScaleSpec extends StringSpec {
minimum?: number;
maximum?: number;
format?: 'float' | 'int64';
}

export const getNumberWithScaleValidator = (params: GetNumberWithScaleValidatorParams = {}) => {
const {
ignoreRequiredCheck,
ignoreSpaceStartCheck,
ignoreSpaceEndCheck,
ignoreNumberCheck,
ignoreMaximumCheck,
ignoreMinimumCheck,
ignoreIntCheck,
ignoreDotEnd,
ignoreZeroStart,
ignoreInvalidZeroFormat,
ignoreZeroEnd,
customErrorMessages,
} = params;

// eslint-disable-next-line complexity
return (spec: StringSpec, value = '') => {
const errorMessages = {...ErrorMessages, ...customErrorMessages};
const numericSpec = spec as NumberWithScaleSpec;

const stringValue = String(value);

if (!ignoreRequiredCheck && spec.required && !stringValue.length) {
return errorMessages.REQUIRED;
}

if (stringValue.length) {
if (!ignoreSpaceStartCheck && !stringValue[0].trim()) {
return errorMessages.SPACE_START;
}

if (!ignoreSpaceEndCheck && !stringValue[stringValue.length - 1].trim()) {
return errorMessages.SPACE_END;
}

if (!ignoreDotEnd && stringValue[stringValue.length - 1] === '.') {
return errorMessages.DOT_END;
}

if (!ignoreNumberCheck && !isFloat(stringValue)) {
return errorMessages.NUMBER;
}

if (
!ignoreZeroStart &&
((stringValue.length > 1 && stringValue[0] === '0' && stringValue[1] !== '.') ||
(stringValue.length > 2 &&
stringValue.substring(0, 2) === '-0' &&
stringValue[2] !== '.'))
) {
return errorMessages.ZERO_START;
}

if (
!ignoreInvalidZeroFormat &&
stringValue.trim().length > 1 &&
Number(stringValue.trim()) === 0
) {
return errorMessages.INVALID_ZERO_FORMAT;
}

if (
!ignoreZeroEnd &&
!isInt(stringValue) &&
stringValue[stringValue.length - 1] === '0'
) {
return errorMessages.ZERO_END;
}
}

if (
!ignoreMaximumCheck &&
isNumber(numericSpec.maximum) &&
stringValue.length &&
Number(stringValue) > numericSpec.maximum
) {
const scaled = getScaledLimit(spec, numericSpec.maximum);
return scaled
? errorMessages.maxNumberWithScale(scaled.count, scaled.scaleTitle)
: errorMessages.maxNumber(numericSpec.maximum);
}

if (
!ignoreMinimumCheck &&
isNumber(numericSpec.minimum) &&
stringValue.length &&
numericSpec.minimum > Number(stringValue)
) {
const scaled = getScaledLimit(spec, numericSpec.minimum);
return scaled
? errorMessages.minNumberWithScale(scaled.count, scaled.scaleTitle)
: errorMessages.minNumber(numericSpec.minimum);
}

if (isString(numericSpec.format) && stringValue.length) {
if (!ignoreIntCheck && numericSpec.format === 'int64' && !isInt(stringValue)) {
return errorMessages.INT;
}
}

return false;
};
};

export interface GetObjectValidatorParams extends CommonValidatorParams {}

export const getObjectValidator = (params: GetObjectValidatorParams = {}) => {
Expand Down
2 changes: 1 addition & 1 deletion src/stories/StringNumberWithScale.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default {

const baseSpec: StringSpec = {
type: SpecTypes.String,
validator: 'number',
validator: 'number_with_scale',
viewSpec: {
type: 'number_with_scale',
layout: 'row',
Expand Down
2 changes: 1 addition & 1 deletion src/stories/components/InputPreview/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ const sizeParams: ObjectSpec = {
viewSpec: {type: 'base', layout: 'table_item'},
},
factor: {
type: SpecTypes.Number,
type: SpecTypes.String,
viewSpec: {type: 'base', layout: 'table_item'},
},
title: {
Expand Down
Loading