Skip to content

Commit e40035e

Browse files
authored
fix(designer): revert No longer escapes characters in token expressions (#6535)
Revert "feat(designer): No longer escapes characters in token expressions (#6131)" This reverts commit a3f6e31.
1 parent 0674cf9 commit e40035e

File tree

7 files changed

+71
-174
lines changed

7 files changed

+71
-174
lines changed

libs/designer-ui/src/lib/editor/base/utils/keyvalueitem.ts

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { isEmpty } from '../../../dictionary/expandeddictionary';
33
import type { ValueSegment } from '../../models/parameter';
44
import { createLiteralValueSegment, insertQutationForStringType } from './helper';
55
import { convertSegmentsToString } from './parsesegments';
6-
import { escapeString } from '@microsoft/logic-apps-shared';
6+
import { isNumber, isBoolean, escapeString } from '@microsoft/logic-apps-shared';
77

88
export interface KeyValueItem {
99
id: string;
@@ -24,7 +24,7 @@ export const convertKeyValueItemToSegments = (items: KeyValueItem[], keyType?: s
2424

2525
for (let index = 0; index < itemsToConvert.length; index++) {
2626
const { key, value } = itemsToConvert[index];
27-
27+
// todo: we should have some way of handle formatting better
2828
const convertedKeyType = convertValueType(key, keyType);
2929
const updatedKey = key.map((segment) => {
3030
return {
@@ -41,7 +41,6 @@ export const convertKeyValueItemToSegments = (items: KeyValueItem[], keyType?: s
4141
};
4242
});
4343

44-
// wrap key and value with quotation marks if they are string type
4544
insertQutationForStringType(parsedItems, convertedKeyType);
4645
parsedItems.push(...updatedKey);
4746
insertQutationForStringType(parsedItems, convertedKeyType);
@@ -61,17 +60,8 @@ export const convertValueType = (value: ValueSegment[], type?: string): string |
6160
return type;
6261
}
6362
const stringSegments = convertSegmentsToString(value).trim();
64-
if (isNonString(stringSegments)) {
65-
return type;
66-
}
67-
return constants.SWAGGER.TYPE.STRING;
68-
};
63+
const isExpressionOrObject = (stringSegments.startsWith('@{') || stringSegments.startsWith('{')) && stringSegments.endsWith('}');
64+
const isKnownType = isExpressionOrObject || isNumber(stringSegments) || isBoolean(stringSegments) || /^\[.*\]$/.test(stringSegments);
6965

70-
const isNonString = (value: string): boolean => {
71-
try {
72-
const parsedValue = JSON.parse(value);
73-
return parsedValue === null || Array.isArray(parsedValue) || typeof parsedValue !== 'string';
74-
} catch {
75-
return false;
76-
}
66+
return isKnownType ? type : constants.SWAGGER.TYPE.STRING;
7767
};

libs/designer/src/lib/core/utils/parameters/__test__/helper.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@ describe('core/utils/parameters/helper', () => {
411411
];
412412

413413
expect(parameterValueToJSONString(parameterValue, /* applyCasting */ false, /* forValidation */ true)).toBe(
414-
'{\n "newUnb3_1": @{xpath(xml(triggerBody()), \'string(/*[local-name()="DynamicsSOCSV"])\')}\n}'
414+
'{"newUnb3_1":"@xpath(xml(triggerBody()), \'string(/*[local-name()=\\"DynamicsSOCSV\\"])\')"}'
415415
);
416416
});
417417

@@ -485,7 +485,7 @@ describe('core/utils/parameters/helper', () => {
485485
];
486486

487487
expect(parameterValueToJSONString(parameterValue, /* applyCasting */ false, /* forValidation */ true)).toBe(
488-
'{\n "newUnb3_1": "@{xpath(xml(triggerBody()), \'string(/*[local-name()="DynamicsSOCSV"])\')}"\n}'
488+
'{"newUnb3_1":"@{xpath(xml(triggerBody()), \'string(/*[local-name()=\\"DynamicsSOCSV\\"])\')}"}'
489489
);
490490
});
491491

libs/designer/src/lib/core/utils/parameters/helper.ts

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,6 @@ import {
117117
isBodySegment,
118118
canStringBeConverted,
119119
splitAtIndex,
120-
unescapeString,
121120
} from '@microsoft/logic-apps-shared';
122121
import type {
123122
AuthProps,
@@ -788,8 +787,8 @@ function toAuthenticationViewModel(value: any): { type: AuthenticationType; auth
788787
type: value.type,
789788
authenticationValue: {
790789
basic: {
791-
basicUsername: loadParameterValueFromString(value.username, { parameterType: constants.SWAGGER.TYPE.STRING }),
792-
basicPassword: loadParameterValueFromString(value.password, { parameterType: constants.SWAGGER.TYPE.STRING }),
790+
basicUsername: loadParameterValueFromString(value.username),
791+
basicPassword: loadParameterValueFromString(value.password),
793792
},
794793
},
795794
};
@@ -798,8 +797,8 @@ function toAuthenticationViewModel(value: any): { type: AuthenticationType; auth
798797
type: value.type,
799798
authenticationValue: {
800799
clientCertificate: {
801-
clientCertificatePfx: loadParameterValueFromString(value.pfx, { parameterType: constants.SWAGGER.TYPE.STRING }),
802-
clientCertificatePassword: loadParameterValueFromString(value.password, { parameterType: constants.SWAGGER.TYPE.STRING }),
800+
clientCertificatePfx: loadParameterValueFromString(value.pfx),
801+
clientCertificatePassword: loadParameterValueFromString(value.password),
803802
},
804803
},
805804
};
@@ -809,14 +808,14 @@ function toAuthenticationViewModel(value: any): { type: AuthenticationType; auth
809808
type: value.type,
810809
authenticationValue: {
811810
aadOAuth: {
812-
oauthTenant: loadParameterValueFromString(value.tenant, { parameterType: constants.SWAGGER.TYPE.STRING }),
813-
oauthAudience: loadParameterValueFromString(value.audience, { parameterType: constants.SWAGGER.TYPE.STRING }),
814-
oauthAuthority: loadParameterValueFromString(value.authority, { parameterType: constants.SWAGGER.TYPE.STRING }),
815-
oauthClientId: loadParameterValueFromString(value.clientId, { parameterType: constants.SWAGGER.TYPE.STRING }),
811+
oauthTenant: loadParameterValueFromString(value.tenant),
812+
oauthAudience: loadParameterValueFromString(value.audience),
813+
oauthAuthority: loadParameterValueFromString(value.authority),
814+
oauthClientId: loadParameterValueFromString(value.clientId),
816815
oauthType: loadOauthType(value),
817-
oauthTypeSecret: loadParameterValueFromString(value.secret, { parameterType: constants.SWAGGER.TYPE.STRING }),
818-
oauthTypeCertificatePfx: loadParameterValueFromString(value.pfx, { parameterType: constants.SWAGGER.TYPE.STRING }),
819-
oauthTypeCertificatePassword: loadParameterValueFromString(value.password, { parameterType: constants.SWAGGER.TYPE.STRING }),
816+
oauthTypeSecret: loadParameterValueFromString(value.secret),
817+
oauthTypeCertificatePfx: loadParameterValueFromString(value.pfx),
818+
oauthTypeCertificatePassword: loadParameterValueFromString(value.password),
820819
},
821820
},
822821
};
@@ -836,7 +835,7 @@ function toAuthenticationViewModel(value: any): { type: AuthenticationType; auth
836835
type: value.type,
837836
authenticationValue: {
838837
msi: {
839-
msiAudience: loadParameterValueFromString(value.audience, { parameterType: constants.SWAGGER.TYPE.STRING }),
838+
msiAudience: loadParameterValueFromString(value.audience),
840839
msiIdentity: value.identity,
841840
},
842841
},
@@ -955,7 +954,7 @@ export function convertToTokenExpression(value: any): string {
955954
return value.toString();
956955
}
957956

958-
export function convertToValueSegments(value: any, shouldUncast: boolean, parameterType?: string): ValueSegment[] {
957+
export function convertToValueSegments(value: any, shouldUncast: boolean, parameterType: string): ValueSegment[] {
959958
try {
960959
const convertor = new ValueSegmentConvertor({
961960
shouldUncast,
@@ -3618,7 +3617,6 @@ export function parameterValueToJSONString(parameterValue: ValueSegment[], apply
36183617
let tokenExpression: string = expression.value;
36193618

36203619
if (isTokenValueSegment(expression)) {
3621-
const stringifiedTokenExpression = tokenExpression;
36223620
// Note: Stringify the token expression to escape double quotes and other characters which must be escaped in JSON.
36233621
if (shouldInterpolate) {
36243622
if (applyCasting) {
@@ -3631,15 +3629,20 @@ export function parameterValueToJSONString(parameterValue: ValueSegment[], apply
36313629
);
36323630
}
36333631

3632+
const stringifiedTokenExpression = JSON.stringify(tokenExpression).slice(1, -1);
36343633
tokenExpression = `@{${stringifiedTokenExpression}}`;
36353634
} else {
36363635
// Add quotes around tokens. Tokens directly after a literal need a leading quote, and those before another literal need an ending quote.
36373636
const lastExpressionWasLiteral = i > 0 && updatedParameterValue[i - 1].type !== ValueSegmentType.TOKEN;
36383637
const nextExpressionIsLiteral =
36393638
i < updatedParameterValue.length - 1 && updatedParameterValue[i + 1].type !== ValueSegmentType.TOKEN;
3639+
3640+
const stringifiedTokenExpression = JSON.stringify(tokenExpression).slice(1, -1);
36403641
tokenExpression = `@${stringifiedTokenExpression}`;
3641-
tokenExpression = lastExpressionWasLiteral ? `"${tokenExpression}` : tokenExpression;
3642-
tokenExpression = nextExpressionIsLiteral ? `${tokenExpression}"` : `${tokenExpression}`;
3642+
// eslint-disable-next-line no-useless-escape
3643+
tokenExpression = lastExpressionWasLiteral ? `\"${tokenExpression}` : tokenExpression;
3644+
// eslint-disable-next-line no-useless-escape
3645+
tokenExpression = nextExpressionIsLiteral ? `${tokenExpression}\"` : `${tokenExpression}`;
36433646
}
36443647

36453648
parameterValueString += tokenExpression;
@@ -3767,14 +3770,13 @@ function parameterValueToStringWithoutCasting(value: ValueSegment[], forValidati
37673770
const shouldInterpolateTokens = (value.length > 1 || shouldInterpolateSingleToken) && value.some(isTokenValueSegment);
37683771

37693772
return value
3770-
.map((segment) => {
3771-
const { value: segmentValue } = segment;
3772-
if (isTokenValueSegment(segment)) {
3773-
const token = forValidation ? segmentValue || null : unescapeString(segmentValue);
3774-
return shouldInterpolateTokens ? `@{${token}}` : `@${token}`;
3773+
.map((expression) => {
3774+
let expressionValue = forValidation ? expression.value || null : expression.value;
3775+
if (isTokenValueSegment(expression)) {
3776+
expressionValue = shouldInterpolateTokens ? `@{${expressionValue}}` : `@${expressionValue}`;
37753777
}
37763778

3777-
return forValidation ? segmentValue || null : segmentValue;
3779+
return expressionValue;
37783780
})
37793781
.join('');
37803782
}

libs/designer/src/lib/core/utils/parameters/segment.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import {
1818
isNullOrUndefined,
1919
startsWith,
2020
UnsupportedException,
21-
escapeString,
2221
} from '@microsoft/logic-apps-shared';
2322

2423
/**
@@ -58,7 +57,7 @@ export class ValueSegmentConvertor {
5857
* @arg {any} value - The value.
5958
* @return {ValueSegment[]}
6059
*/
61-
public convertToValueSegments(value: any, parameterType?: string): ValueSegment[] {
60+
public convertToValueSegments(value: any, parameterType: string = constants.SWAGGER.TYPE.ANY): ValueSegment[] {
6261
if (isNullOrUndefined(value)) {
6362
return [createLiteralValueSegment('')];
6463
}
@@ -108,12 +107,11 @@ export class ValueSegmentConvertor {
108107
return [this._createLiteralValueSegment(section)];
109108
}
110109

111-
private _convertStringToValueSegments(value: string, parameterType?: string): ValueSegment[] {
110+
private _convertStringToValueSegments(value: string, parameterType: string): ValueSegment[] {
112111
if (isTemplateExpression(value)) {
113112
const expression = ExpressionParser.parseTemplateExpression(value);
114113
return this._convertTemplateExpressionToValueSegments(expression);
115114
}
116-
117115
const isSpecialValue = ['true', 'false', 'null'].includes(value) || /^-?\d+$/.test(value);
118116
const stringValue = parameterType === constants.SWAGGER.TYPE.ANY && isSpecialValue ? `"${value}"` : value;
119117
return [this._createLiteralValueSegment(stringValue)];
@@ -188,11 +186,10 @@ export class ValueSegmentConvertor {
188186
return dynamicContentTokenSegment;
189187
}
190188
// Note: We need to get the expression value if this is a sub expression resulted from uncasting.
191-
const value = escapeString(
189+
const value =
192190
expression.startPosition === 0
193191
? expression.expression
194-
: expression.expression.substring(expression.startPosition, expression.endPosition)
195-
);
192+
: expression.expression.substring(expression.startPosition, expression.endPosition);
196193
return this._createExpressionTokenValueSegment(value, expression);
197194
}
198195

libs/designer/src/lib/core/utils/validation.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -429,10 +429,16 @@ const validateFloatingActionMenuOutputsEditor = (editorViewModel: FloatingAction
429429
};
430430

431431
function shouldValidateJSON(expressions: ValueSegment[]): boolean {
432-
const firstSegmentValue = expressions[0]?.token?.value;
433-
return firstSegmentValue
434-
? firstSegmentValue.startsWith('@@') || firstSegmentValue.startsWith('@{') || !firstSegmentValue.startsWith('@')
435-
: true;
432+
const shouldValidate = true;
433+
434+
if (shouldValidate && expressions.length) {
435+
const firstSegmentValue = expressions[0].token?.value;
436+
if (firstSegmentValue) {
437+
return startsWith(firstSegmentValue, '@@') || startsWith(firstSegmentValue, '@{') || !startsWith(firstSegmentValue, '@');
438+
}
439+
}
440+
441+
return shouldValidate;
436442
}
437443

438444
export function parameterHasOnlyTokenBinding(parameterValue: ValueSegment[]): boolean {
Lines changed: 21 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { escapeString, idDisplayCase, labelCase, canStringBeConverted, unescapeString } from '../stringFunctions';
1+
import { escapeString, idDisplayCase, labelCase, canStringBeConverted } from '../stringFunctions';
22
import { describe, it, expect } from 'vitest';
33
describe('label_case', () => {
44
it('should replace _ with spaces', () => {
@@ -28,6 +28,26 @@ describe('idDisplayCase', () => {
2828
});
2929
});
3030

31+
describe('escapeString', () => {
32+
it('should correctly escape backslashes', () => {
33+
expect(escapeString('\\')).toEqual('\\\\');
34+
expect(escapeString('Test\\Test')).toEqual('Test\\\\Test');
35+
});
36+
37+
it('should correctly escape newline characters', () => {
38+
expect(escapeString('\n')).toEqual('\\n');
39+
expect(escapeString('Test\nTest')).toEqual('Test\\nTest');
40+
});
41+
42+
it('should correctly escape backslashes and newline characters together', () => {
43+
expect(escapeString('\\\n')).toEqual('\\\\\\n');
44+
expect(escapeString('Test\\\nTest')).toEqual('Test\\\\\\nTest');
45+
});
46+
47+
it('should handle an empty string', () => {
48+
expect(escapeString('')).toEqual('');
49+
});
50+
});
3151
describe('canStringBeConverted', () => {
3252
it('should return false for non-string inputs', () => {
3353
expect(canStringBeConverted(123 as any)).toBe(false);
@@ -69,91 +89,3 @@ describe('canStringBeConverted', () => {
6989
expect(canStringBeConverted('{"a", "b", "c"}')).toBe(false);
7090
});
7191
});
72-
73-
describe('unescapeString', () => {
74-
it('unescapes newline characters', () => {
75-
const input = 'Hello\\nWorld';
76-
const expectedOutput = 'Hello\nWorld';
77-
const result = unescapeString(input);
78-
expect(result).toBe(expectedOutput);
79-
});
80-
81-
it('unescapes carriage return characters', () => {
82-
const input = 'Hello\\rWorld';
83-
const expectedOutput = 'Hello\rWorld';
84-
const result = unescapeString(input);
85-
expect(result).toBe(expectedOutput);
86-
});
87-
88-
it('unescapes tab characters', () => {
89-
const input = 'Hello\\tWorld';
90-
const expectedOutput = 'Hello\tWorld';
91-
const result = unescapeString(input);
92-
expect(result).toBe(expectedOutput);
93-
});
94-
95-
it('unescapes vertical tab characters', () => {
96-
const input = 'Hello\\vWorld';
97-
const expectedOutput = 'Hello\vWorld';
98-
const result = unescapeString(input);
99-
expect(result).toBe(expectedOutput);
100-
});
101-
102-
it('returns the same string if there are no escape sequences', () => {
103-
const input = 'Hello World';
104-
const expectedOutput = 'Hello World';
105-
const result = unescapeString(input);
106-
expect(result).toBe(expectedOutput);
107-
});
108-
});
109-
110-
describe('escapeString', () => {
111-
it('escapes newline characters', () => {
112-
const input = 'Hello\nWorld';
113-
const expectedOutput = 'Hello\\nWorld';
114-
const result = escapeString(input);
115-
expect(result).toBe(expectedOutput);
116-
});
117-
118-
it('escapes carriage return characters', () => {
119-
const input = 'Hello\rWorld';
120-
const expectedOutput = 'Hello\\rWorld';
121-
const result = escapeString(input);
122-
expect(result).toBe(expectedOutput);
123-
});
124-
125-
it('escapes tab characters', () => {
126-
const input = 'Hello\tWorld';
127-
const expectedOutput = 'Hello\\tWorld';
128-
const result = escapeString(input);
129-
expect(result).toBe(expectedOutput);
130-
});
131-
132-
it('escapes vertical tab characters', () => {
133-
const input = 'Hello\vWorld';
134-
const expectedOutput = 'Hello\\vWorld';
135-
const result = escapeString(input);
136-
expect(result).toBe(expectedOutput);
137-
});
138-
139-
it('returns the same string if there are no special characters', () => {
140-
const input = 'Hello World';
141-
const expectedOutput = 'Hello World';
142-
const result = escapeString(input);
143-
expect(result).toBe(expectedOutput);
144-
});
145-
146-
it('should correctly escape newline characters', () => {
147-
expect(escapeString('\n')).toEqual('\\n');
148-
expect(escapeString('Test\nTest')).toEqual('Test\\nTest');
149-
});
150-
151-
it('should correctly escape backslashes and newline characters together', () => {
152-
expect(escapeString('\\\n')).toEqual('\\\\n');
153-
expect(escapeString('Test\\\nTest')).toEqual('Test\\\\nTest');
154-
});
155-
156-
it('should handle an empty string', () => {
157-
expect(escapeString('')).toEqual('');
158-
});
159-
});

0 commit comments

Comments
 (0)