From da62c93157e49adb6f4af400c17faa12951afc6a Mon Sep 17 00:00:00 2001 From: Krrish Mittal Date: Fri, 23 Jan 2026 09:50:58 -0800 Subject: [PATCH] fix(designer-ui): Fix dictionary serialization with compound token expressions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../base/utils/__test__/keyvalueitem.spec.ts | 84 +++++++++++++++++++ .../src/lib/editor/base/utils/keyvalueitem.ts | 7 ++ 2 files changed, 91 insertions(+) create mode 100644 libs/designer-ui/src/lib/editor/base/utils/__test__/keyvalueitem.spec.ts diff --git a/libs/designer-ui/src/lib/editor/base/utils/__test__/keyvalueitem.spec.ts b/libs/designer-ui/src/lib/editor/base/utils/__test__/keyvalueitem.spec.ts new file mode 100644 index 00000000000..c6f1b756379 --- /dev/null +++ b/libs/designer-ui/src/lib/editor/base/utils/__test__/keyvalueitem.spec.ts @@ -0,0 +1,84 @@ +import { describe, expect, it } from 'vitest'; +import { ValueSegmentType } from '../../../models/parameter'; +import { convertValueType, convertKeyValueItemToSegments } from '../keyvalueitem'; +import { convertSegmentsToString } from '../parsesegments'; + +describe('keyvalueitem utilities', () => { + describe('convertValueType', () => { + it('should return the specified type when type is not "any"', () => { + const value = [{ id: '1', type: ValueSegmentType.LITERAL, value: 'test' }]; + expect(convertValueType(value, 'string')).toBe('string'); + expect(convertValueType(value, 'integer')).toBe('integer'); + expect(convertValueType(value, 'boolean')).toBe('boolean'); + }); + + it('should return STRING for literal values when type is "any"', () => { + const value = [{ id: '1', type: ValueSegmentType.LITERAL, value: 'hello' }]; + expect(convertValueType(value, 'any')).toBe('string'); + }); + + it('should return STRING for compound expressions (tokens mixed with literals)', () => { + // Simulates: @{variables('year')}-@{variables('month')}-@{variables('date')} + const value = [ + { id: '1', type: ValueSegmentType.TOKEN, value: "variables('year')" }, + { id: '2', type: ValueSegmentType.LITERAL, value: '-' }, + { id: '3', type: ValueSegmentType.TOKEN, value: "variables('month')" }, + { id: '4', type: ValueSegmentType.LITERAL, value: '-' }, + { id: '5', type: ValueSegmentType.TOKEN, value: "variables('date')" }, + ]; + expect(convertValueType(value, 'any')).toBe('string'); + expect(convertValueType(value, undefined)).toBe('string'); + }); + + it('should preserve type for single token values', () => { + // A single token like @{variables('count')} should keep its type (e.g., for integer parameters) + const value = [{ id: '1', type: ValueSegmentType.TOKEN, value: "variables('count')" }]; + expect(convertValueType(value, 'any')).toBe('any'); + expect(convertValueType(value, undefined)).toBe(undefined); + }); + + it('should return STRING for non-string parseable values', () => { + // Numbers, booleans, null should not be treated as strings + const numberValue = [{ id: '1', type: ValueSegmentType.LITERAL, value: '123' }]; + expect(convertValueType(numberValue, 'any')).toBe('any'); + + const boolValue = [{ id: '1', type: ValueSegmentType.LITERAL, value: 'true' }]; + expect(convertValueType(boolValue, 'any')).toBe('any'); + }); + }); + + describe('convertKeyValueItemToSegments', () => { + it('should produce valid JSON structure for compound expression values', () => { + const items = [ + { + id: '1', + key: [{ id: 'k1', type: ValueSegmentType.LITERAL, value: 'DATE' }], + value: [ + { id: 'v1', type: ValueSegmentType.TOKEN, value: "variables('year')" }, + { id: 'v2', type: ValueSegmentType.LITERAL, value: '-' }, + { id: 'v3', type: ValueSegmentType.TOKEN, value: "variables('month')" }, + ], + }, + ]; + + const segments = convertKeyValueItemToSegments(items, 'string', 'any'); + const result = convertSegmentsToString(segments); + + // The value should be quoted since it's a compound expression + expect(result).toContain('"DATE"'); + // The compound expression should be wrapped in quotes to produce valid JSON + expect(result).toMatch(/"DATE"\s*:\s*"/); + }); + + it('should handle empty items', () => { + const items: { + id: string; + key: { id: string; type: string; value: string }[]; + value: { id: string; type: string; value: string }[]; + }[] = []; + const segments = convertKeyValueItemToSegments(items); + expect(segments).toHaveLength(1); + expect(segments[0].value).toBe(''); + }); + }); +}); diff --git a/libs/designer-ui/src/lib/editor/base/utils/keyvalueitem.ts b/libs/designer-ui/src/lib/editor/base/utils/keyvalueitem.ts index 32ddd5ec90e..9d42bd8644d 100644 --- a/libs/designer-ui/src/lib/editor/base/utils/keyvalueitem.ts +++ b/libs/designer-ui/src/lib/editor/base/utils/keyvalueitem.ts @@ -66,6 +66,13 @@ export const convertValueType = (value: ValueSegment[], type?: string): string | return type; } const stringSegments = convertSegmentsToString(value).trim(); + + // If value contains tokens mixed with other content (compound expressions like "@{var1}-@{var2}"), + // treat as string to produce valid JSON. Compound expressions must be quoted strings in JSON. + if (containsTokenSegments(value) && value.length > 1) { + return constants.SWAGGER.TYPE.STRING; + } + if (isNonString(stringSegments) || containsTokenSegments(value)) { return type; }