feat: simplification of default-value specification in FieldMetadata (#4592)
* feat: wip refactor default-value * feat: health check to migrate default value * fix: tests * fix: refactor defaultValue to make it more clean * fix: unit tests * fix: front-end default value
This commit is contained in:
@ -8,25 +8,19 @@ describe('serializeDefaultValue', () => {
|
||||
});
|
||||
|
||||
it('should handle uuid dynamic default value', () => {
|
||||
expect(serializeDefaultValue({ type: 'uuid' })).toBe(
|
||||
'public.uuid_generate_v4()',
|
||||
);
|
||||
expect(serializeDefaultValue('uuid')).toBe('public.uuid_generate_v4()');
|
||||
});
|
||||
|
||||
it('should handle now dynamic default value', () => {
|
||||
expect(serializeDefaultValue({ type: 'now' })).toBe('now()');
|
||||
expect(serializeDefaultValue('now')).toBe('now()');
|
||||
});
|
||||
|
||||
it('should throw BadRequestException for invalid dynamic default value type', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error Just for testing purposes
|
||||
expect(() => serializeDefaultValue({ type: 'invalid' })).toThrow(
|
||||
BadRequestException,
|
||||
);
|
||||
expect(() => serializeDefaultValue('invalid')).toThrow(BadRequestException);
|
||||
});
|
||||
|
||||
it('should handle string static default value', () => {
|
||||
expect(serializeDefaultValue('test')).toBe("'test'");
|
||||
expect(serializeDefaultValue("'test'")).toBe("'test'");
|
||||
});
|
||||
|
||||
it('should handle number static default value', () => {
|
||||
|
||||
@ -10,110 +10,105 @@ describe('validateDefaultValueForType', () => {
|
||||
|
||||
// Dynamic default values
|
||||
it('should validate uuid dynamic default value for UUID type', () => {
|
||||
expect(
|
||||
validateDefaultValueForType(FieldMetadataType.UUID, { type: 'uuid' }),
|
||||
).toBe(true);
|
||||
expect(validateDefaultValueForType(FieldMetadataType.UUID, 'uuid')).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it('should validate now dynamic default value for DATE_TIME type', () => {
|
||||
expect(
|
||||
validateDefaultValueForType(FieldMetadataType.DATE_TIME, { type: 'now' }),
|
||||
validateDefaultValueForType(FieldMetadataType.DATE_TIME, 'now'),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for mismatched dynamic default value', () => {
|
||||
expect(
|
||||
validateDefaultValueForType(FieldMetadataType.UUID, { type: 'now' }),
|
||||
).toBe(false);
|
||||
expect(validateDefaultValueForType(FieldMetadataType.UUID, 'now')).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
// Static default values
|
||||
it('should validate string default value for TEXT type', () => {
|
||||
expect(
|
||||
validateDefaultValueForType(FieldMetadataType.TEXT, { value: 'test' }),
|
||||
).toBe(true);
|
||||
expect(validateDefaultValueForType(FieldMetadataType.TEXT, "'test'")).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return false for invalid string default value for TEXT type', () => {
|
||||
expect(
|
||||
validateDefaultValueForType(FieldMetadataType.TEXT, { value: 123 }),
|
||||
).toBe(false);
|
||||
expect(validateDefaultValueForType(FieldMetadataType.TEXT, 123)).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it('should validate string default value for PHONE type', () => {
|
||||
expect(
|
||||
validateDefaultValueForType(FieldMetadataType.PHONE, {
|
||||
value: '+123456789',
|
||||
}),
|
||||
validateDefaultValueForType(FieldMetadataType.PHONE, "'+123456789'"),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for invalid string default value for PHONE type', () => {
|
||||
expect(
|
||||
validateDefaultValueForType(FieldMetadataType.PHONE, { value: 123 }),
|
||||
).toBe(false);
|
||||
expect(validateDefaultValueForType(FieldMetadataType.PHONE, 123)).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it('should validate string default value for EMAIL type', () => {
|
||||
expect(
|
||||
validateDefaultValueForType(FieldMetadataType.EMAIL, {
|
||||
value: 'test@example.com',
|
||||
}),
|
||||
validateDefaultValueForType(
|
||||
FieldMetadataType.EMAIL,
|
||||
"'test@example.com'",
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for invalid string default value for EMAIL type', () => {
|
||||
expect(
|
||||
validateDefaultValueForType(FieldMetadataType.EMAIL, { value: 123 }),
|
||||
).toBe(false);
|
||||
expect(validateDefaultValueForType(FieldMetadataType.EMAIL, 123)).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it('should validate number default value for NUMBER type', () => {
|
||||
expect(
|
||||
validateDefaultValueForType(FieldMetadataType.NUMBER, { value: 100 }),
|
||||
).toBe(true);
|
||||
expect(validateDefaultValueForType(FieldMetadataType.NUMBER, 100)).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return false for invalid number default value for NUMBER type', () => {
|
||||
expect(
|
||||
validateDefaultValueForType(FieldMetadataType.NUMBER, { value: '100' }),
|
||||
).toBe(false);
|
||||
expect(validateDefaultValueForType(FieldMetadataType.NUMBER, '100')).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it('should validate number default value for PROBABILITY type', () => {
|
||||
expect(
|
||||
validateDefaultValueForType(FieldMetadataType.PROBABILITY, {
|
||||
value: 0.5,
|
||||
}),
|
||||
validateDefaultValueForType(FieldMetadataType.PROBABILITY, 0.5),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for invalid number default value for PROBABILITY type', () => {
|
||||
expect(
|
||||
validateDefaultValueForType(FieldMetadataType.PROBABILITY, {
|
||||
value: '50%',
|
||||
}),
|
||||
validateDefaultValueForType(FieldMetadataType.PROBABILITY, '50%'),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should validate boolean default value for BOOLEAN type', () => {
|
||||
expect(
|
||||
validateDefaultValueForType(FieldMetadataType.BOOLEAN, { value: true }),
|
||||
).toBe(true);
|
||||
expect(validateDefaultValueForType(FieldMetadataType.BOOLEAN, true)).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return false for invalid boolean default value for BOOLEAN type', () => {
|
||||
expect(
|
||||
validateDefaultValueForType(FieldMetadataType.BOOLEAN, { value: 'true' }),
|
||||
).toBe(false);
|
||||
expect(validateDefaultValueForType(FieldMetadataType.BOOLEAN, 'true')).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
// LINK type
|
||||
it('should validate LINK default value', () => {
|
||||
expect(
|
||||
validateDefaultValueForType(FieldMetadataType.LINK, {
|
||||
label: 'http://example.com',
|
||||
url: 'Example',
|
||||
label: "'http://example.com'",
|
||||
url: "'Example'",
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
@ -134,7 +129,7 @@ describe('validateDefaultValueForType', () => {
|
||||
expect(
|
||||
validateDefaultValueForType(FieldMetadataType.CURRENCY, {
|
||||
amountMicros: '100',
|
||||
currencyCode: 'USD',
|
||||
currencyCode: "'USD'",
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
@ -144,7 +139,7 @@ describe('validateDefaultValueForType', () => {
|
||||
validateDefaultValueForType(
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error Just for testing purposes
|
||||
{ amountMicros: 100, currencyCode: 'USD' },
|
||||
{ amountMicros: 100, currencyCode: "'USD'" },
|
||||
FieldMetadataType.CURRENCY,
|
||||
),
|
||||
).toBe(false);
|
||||
@ -153,9 +148,7 @@ describe('validateDefaultValueForType', () => {
|
||||
// Unknown type
|
||||
it('should return false for unknown type', () => {
|
||||
expect(
|
||||
validateDefaultValueForType('unknown' as FieldMetadataType, {
|
||||
value: 'test',
|
||||
}),
|
||||
validateDefaultValueForType('unknown' as FieldMetadataType, "'test'"),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@ -9,23 +9,21 @@ export function generateDefaultValue(
|
||||
case FieldMetadataType.TEXT:
|
||||
case FieldMetadataType.PHONE:
|
||||
case FieldMetadataType.EMAIL:
|
||||
return {
|
||||
value: '',
|
||||
};
|
||||
return "''";
|
||||
case FieldMetadataType.FULL_NAME:
|
||||
return {
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
firstName: "''",
|
||||
lastName: "''",
|
||||
};
|
||||
case FieldMetadataType.LINK:
|
||||
return {
|
||||
url: '',
|
||||
label: '',
|
||||
url: "''",
|
||||
label: "''",
|
||||
};
|
||||
case FieldMetadataType.CURRENCY:
|
||||
return {
|
||||
amountMicros: null,
|
||||
currencyCode: '',
|
||||
currencyCode: "''",
|
||||
};
|
||||
default:
|
||||
return null;
|
||||
|
||||
@ -0,0 +1,21 @@
|
||||
import {
|
||||
FieldMetadataDefaultSerializableValue,
|
||||
FieldMetadataFunctionDefaultValue,
|
||||
} from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
|
||||
|
||||
import {
|
||||
FieldMetadataDefaultValueFunctionNames,
|
||||
fieldMetadataDefaultValueFunctionName,
|
||||
} from 'src/engine/metadata-modules/field-metadata/dtos/default-value.input';
|
||||
|
||||
export const isFunctionDefaultValue = (
|
||||
defaultValue: FieldMetadataDefaultSerializableValue,
|
||||
): defaultValue is FieldMetadataFunctionDefaultValue => {
|
||||
return (
|
||||
typeof defaultValue === 'string' &&
|
||||
!defaultValue.startsWith("'") &&
|
||||
Object.values(fieldMetadataDefaultValueFunctionName).includes(
|
||||
defaultValue as FieldMetadataDefaultValueFunctionNames,
|
||||
)
|
||||
);
|
||||
};
|
||||
@ -2,7 +2,8 @@ import { BadRequestException } from '@nestjs/common';
|
||||
|
||||
import { FieldMetadataDefaultSerializableValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
|
||||
|
||||
import { serializeTypeDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/serialize-type-default-value.util';
|
||||
import { isFunctionDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/is-function-default-value.util';
|
||||
import { serializeFunctionDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/serialize-function-default-value.util';
|
||||
|
||||
export const serializeDefaultValue = (
|
||||
defaultValue?: FieldMetadataDefaultSerializableValue,
|
||||
@ -11,13 +12,10 @@ export const serializeDefaultValue = (
|
||||
return null;
|
||||
}
|
||||
|
||||
// Dynamic default values
|
||||
if (
|
||||
!Array.isArray(defaultValue) &&
|
||||
typeof defaultValue === 'object' &&
|
||||
'type' in defaultValue
|
||||
) {
|
||||
const serializedTypeDefaultValue = serializeTypeDefaultValue(defaultValue);
|
||||
// Function default values
|
||||
if (isFunctionDefaultValue(defaultValue)) {
|
||||
const serializedTypeDefaultValue =
|
||||
serializeFunctionDefaultValue(defaultValue);
|
||||
|
||||
if (!serializedTypeDefaultValue) {
|
||||
throw new BadRequestException('Invalid default value');
|
||||
@ -27,8 +25,8 @@ export const serializeDefaultValue = (
|
||||
}
|
||||
|
||||
// Static default values
|
||||
if (typeof defaultValue === 'string') {
|
||||
return `'${defaultValue}'`;
|
||||
if (typeof defaultValue === 'string' && defaultValue.startsWith("'")) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
if (typeof defaultValue === 'number') {
|
||||
@ -51,5 +49,5 @@ export const serializeDefaultValue = (
|
||||
return `'${JSON.stringify(defaultValue)}'`;
|
||||
}
|
||||
|
||||
throw new BadRequestException('Invalid default value');
|
||||
throw new BadRequestException(`Invalid default value "${defaultValue}"`);
|
||||
};
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
import { FieldMetadataFunctionDefaultValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
|
||||
|
||||
export const serializeFunctionDefaultValue = (
|
||||
defaultValue?: FieldMetadataFunctionDefaultValue,
|
||||
) => {
|
||||
switch (defaultValue) {
|
||||
case 'uuid':
|
||||
return 'public.uuid_generate_v4()';
|
||||
case 'now':
|
||||
return 'now()';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@ -1,18 +0,0 @@
|
||||
import { FieldMetadataDynamicDefaultValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
|
||||
|
||||
export const serializeTypeDefaultValue = (
|
||||
defaultValue?: FieldMetadataDynamicDefaultValue,
|
||||
) => {
|
||||
if (!defaultValue?.type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (defaultValue.type) {
|
||||
case 'uuid':
|
||||
return 'public.uuid_generate_v4()';
|
||||
case 'now':
|
||||
return 'now()';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@ -1,7 +1,10 @@
|
||||
import { plainToInstance } from 'class-transformer';
|
||||
import { validateSync } from 'class-validator';
|
||||
|
||||
import { FieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
|
||||
import {
|
||||
FieldMetadataClassValidation,
|
||||
FieldMetadataDefaultValue,
|
||||
} from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
|
||||
|
||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import {
|
||||
@ -14,21 +17,22 @@ import {
|
||||
FieldMetadataDefaultValueNumber,
|
||||
FieldMetadataDefaultValueString,
|
||||
FieldMetadataDefaultValueStringArray,
|
||||
FieldMetadataDynamicDefaultValueNow,
|
||||
FieldMetadataDynamicDefaultValueUuid,
|
||||
FieldMetadataDefaultValueNowFunction,
|
||||
FieldMetadataDefaultValueUuidFunction,
|
||||
} from 'src/engine/metadata-modules/field-metadata/dtos/default-value.input';
|
||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||
|
||||
export const defaultValueValidatorsMap = {
|
||||
[FieldMetadataType.UUID]: [
|
||||
FieldMetadataDefaultValueString,
|
||||
FieldMetadataDynamicDefaultValueUuid,
|
||||
FieldMetadataDefaultValueUuidFunction,
|
||||
],
|
||||
[FieldMetadataType.TEXT]: [FieldMetadataDefaultValueString],
|
||||
[FieldMetadataType.PHONE]: [FieldMetadataDefaultValueString],
|
||||
[FieldMetadataType.EMAIL]: [FieldMetadataDefaultValueString],
|
||||
[FieldMetadataType.DATE_TIME]: [
|
||||
FieldMetadataDefaultValueDateTime,
|
||||
FieldMetadataDynamicDefaultValueNow,
|
||||
FieldMetadataDefaultValueNowFunction,
|
||||
],
|
||||
[FieldMetadataType.BOOLEAN]: [FieldMetadataDefaultValueBoolean],
|
||||
[FieldMetadataType.NUMBER]: [FieldMetadataDefaultValueNumber],
|
||||
@ -54,10 +58,14 @@ export const validateDefaultValueForType = (
|
||||
if (!validators) return false;
|
||||
|
||||
const isValid = validators.some((validator) => {
|
||||
const conputedDefaultValue = isCompositeFieldMetadataType(type)
|
||||
? defaultValue
|
||||
: { value: defaultValue };
|
||||
|
||||
const defaultValueInstance = plainToInstance<
|
||||
any,
|
||||
FieldMetadataDefaultValue
|
||||
>(validator, defaultValue);
|
||||
FieldMetadataClassValidation
|
||||
>(validator, conputedDefaultValue as FieldMetadataClassValidation);
|
||||
|
||||
return (
|
||||
validateSync(defaultValueInstance, {
|
||||
|
||||
Reference in New Issue
Block a user