fix: handle string-typed options in transformEnumValue
https://sonarly.com/issue/16534?type=bug `transformEnumValue()` calls `.map()` on the `options` value without checking if it's actually an array. When a client sends `options` as a JSON-encoded string (e.g., `"[{...}]"`) instead of a native JSON array via the `updateOneField` mutation, the `@Transform` decorator passes the raw string to `transformEnumValue()`, causing a `TypeError`. Fix: **`transform-enum-value.ts`**: The function now accepts `string` in addition to `FieldMetadataDefaultOption[]`. When a string is received (from clients sending JSON-encoded options via GraphQL), it parses it with `JSON.parse`. It also guards against non-array parsed values by returning them as-is, letting downstream validation handle the error. **`field-metadata.dto.ts`**: Removed the unsafe `as FieldMetadataDefaultOption[]` cast from the `@Transform` decorator, which was masking the type mismatch at compile time. The `transformEnumValue` function now handles the real runtime types directly. Removed the now-unused `FieldMetadataDefaultOption` import. **`transform-enum-value.spec.ts`**: Added tests covering: undefined input, array input with digit-prefixed values, JSON string input, and JSON string input with digit-prefixed values.
This commit is contained in:
+1
-4
@@ -33,7 +33,6 @@ import {
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { IsValidMetadataName } from 'src/engine/decorators/metadata/is-valid-metadata-name.decorator';
|
||||
import { FieldStandardOverridesDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-standard-overrides.dto';
|
||||
import { type FieldMetadataDefaultOption } from 'src/engine/metadata-modules/field-metadata/dtos/options.input';
|
||||
import { ObjectMetadataDTO } from 'src/engine/metadata-modules/object-metadata/dtos/object-metadata.dto';
|
||||
import { transformEnumValue } from 'src/engine/utils/transform-enum-value';
|
||||
|
||||
@@ -132,9 +131,7 @@ export class FieldMetadataDTO<T extends FieldMetadataType = FieldMetadataType> {
|
||||
@Field(() => GraphQLJSON, { nullable: true })
|
||||
defaultValue?: FieldMetadataDefaultValue<T>;
|
||||
|
||||
@Transform(({ value }) =>
|
||||
transformEnumValue(value as FieldMetadataDefaultOption[]),
|
||||
)
|
||||
@Transform(({ value }) => transformEnumValue(value))
|
||||
@IsOptional()
|
||||
@Field(() => GraphQLJSON, { nullable: true })
|
||||
options?: FieldMetadataOptions<T>;
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { transformEnumValue } from 'src/engine/utils/transform-enum-value';
|
||||
|
||||
describe('transformEnumValue', () => {
|
||||
it('should return undefined when options is undefined', () => {
|
||||
expect(transformEnumValue(undefined)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should prefix option values starting with a digit', () => {
|
||||
const options = [
|
||||
{ value: '1_OPTION', label: 'Option 1', color: 'green' as const },
|
||||
{ value: 'OPTION_2', label: 'Option 2', color: 'blue' as const },
|
||||
];
|
||||
|
||||
const result = transformEnumValue(options);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ value: '_1_OPTION', label: 'Option 1', color: 'green' },
|
||||
{ value: 'OPTION_2', label: 'Option 2', color: 'blue' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should parse and transform options passed as a JSON string', () => {
|
||||
const options = JSON.stringify([
|
||||
{ value: 'STARTUP', label: 'Startup', color: 'green' },
|
||||
{ value: 'SME', label: 'SME', color: 'turquoise' },
|
||||
]);
|
||||
|
||||
const result = transformEnumValue(options);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ value: 'STARTUP', label: 'Startup', color: 'green' },
|
||||
{ value: 'SME', label: 'SME', color: 'turquoise' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should prefix digit-starting values in JSON string options', () => {
|
||||
const options = JSON.stringify([
|
||||
{ value: '3RD_PARTY', label: '3rd Party', color: 'red' },
|
||||
]);
|
||||
|
||||
const result = transformEnumValue(options);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ value: '_3RD_PARTY', label: '3rd Party', color: 'red' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return options unchanged when no values start with a digit', () => {
|
||||
const options = [
|
||||
{ value: 'STARTUP', label: 'Startup', color: 'green' as const },
|
||||
];
|
||||
|
||||
const result = transformEnumValue(options);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ value: 'STARTUP', label: 'Startup', color: 'green' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,20 @@
|
||||
import { type FieldMetadataDefaultOption } from 'src/engine/metadata-modules/field-metadata/dtos/options.input';
|
||||
|
||||
export function transformEnumValue(options?: FieldMetadataDefaultOption[]) {
|
||||
return options?.map((option) => {
|
||||
export function transformEnumValue(
|
||||
options?: FieldMetadataDefaultOption[] | string,
|
||||
) {
|
||||
if (!options) {
|
||||
return options;
|
||||
}
|
||||
|
||||
const parsedOptions =
|
||||
typeof options === 'string' ? JSON.parse(options) : options;
|
||||
|
||||
if (!Array.isArray(parsedOptions)) {
|
||||
return parsedOptions;
|
||||
}
|
||||
|
||||
return parsedOptions.map((option: FieldMetadataDefaultOption) => {
|
||||
if (/^\d/.test(option.value)) {
|
||||
return {
|
||||
...option,
|
||||
|
||||
Reference in New Issue
Block a user