fix(app): preserve colons in OData datetimes (#7415)

This commit is contained in:
cryst
2026-03-27 14:30:21 +00:00
committed by GitHub
parent 13e97f0367
commit ebbe869deb
2 changed files with 65 additions and 17 deletions

View File

@@ -12,6 +12,32 @@ const hasLength = (str) => {
return str.length > 0;
};
const ODATA_PATH_PARAM_REGEX = /(^|[=(,'"`]):([A-Za-z_]\w*)\b/g;
const decodeODataSegment = (segment) => {
try {
return decodeURIComponent(segment);
} catch (error) {
return segment;
}
};
const getODataPathParamNames = (segment) => {
const names = [];
const decodedSegment = decodeODataSegment(segment);
let match;
while ((match = ODATA_PATH_PARAM_REGEX.exec(decodedSegment))) {
if (match[2]) {
names.push(match[2]);
}
}
ODATA_PATH_PARAM_REGEX.lastIndex = 0;
return names;
};
export const parsePathParams = (url) => {
let uri = url.slice();
@@ -49,17 +75,12 @@ export const parsePathParams = (url) => {
// 1. EntitySet('key') or EntitySet(key)
// 2. EntitySet(Key1=value1,Key2=value2)
// 3. Function(param=value)
if (!/^[A-Za-z0-9_.-]+\([^)]*\)$/.test(segment)) {
if (!/^[A-Za-z0-9_.-]+\([^)]*\)$/.test(decodeODataSegment(segment))) {
return;
}
const paramRegex = /[:](\w+)/g;
let match;
while ((match = paramRegex.exec(segment))) {
if (!match[1]) continue;
let name = match[1].replace(/[')"`]+$/, '');
name = name.replace(/^[('"`]+/, '');
const paramNames = getODataPathParamNames(segment);
for (const name of paramNames) {
if (name && !foundParams.has(name)) {
foundParams.add(name);
}
@@ -127,23 +148,18 @@ export const interpolateUrlPathParams = (url, params, variables = {}, options =
// 1. EntitySet('key') or EntitySet(key)
// 2. EntitySet(Key1=value1,Key2=value2)
// 3. Function(param=value)
if (!/^[A-Za-z0-9_.-]+\([^)]*\)$/.test(segment)) {
if (!/^[A-Za-z0-9_.-]+\([^)]*\)$/.test(decodeODataSegment(segment))) {
return segment;
}
const regex = /[:](\w+)/g;
let match;
let result = segment;
while ((match = regex.exec(segment))) {
if (!match[1]) continue;
let name = match[1].replace(/[')"`]+$/, '');
name = name.replace(/^[('"`]+/, '');
const paramNames = getODataPathParamNames(segment);
for (const name of paramNames) {
if (!name) continue;
const pathParam = params.find((p) => p?.name === name && p?.type === 'path');
if (pathParam) {
result = result.replace(':' + match[1], pathParam.value);
result = result.replace(`:${name}`, pathParam.value);
}
}
return result;

View File

@@ -259,6 +259,14 @@ describe('Url Utils - OData parameters', () => {
const params = parsePathParams('https://example.com/odata/Products?$filter=Category eq \'{{category}}\'&$orderby={{sortField}}');
expect(params).toEqual([]);
});
it('should not treat colons inside OData datetime values as path params', () => {
const params = parsePathParams(
'https://api10.successfactors.com/odata/v2/EmpJob(seqNumber=1L,startDate=datetime\'2021-08-29T00:00:00\',userId=\'213668\')?$format=json'
);
expect(params).toEqual([]);
});
});
describe('Url Utils - splitOnFirst', () => {
@@ -463,4 +471,28 @@ describe('Url Utils - interpolateUrlPathParams with { raw: true }', () => {
expect(result).toEqual('https://example.com/api/:id');
});
it('should preserve OData datetime colons when no path params are present', () => {
const url
= 'https://api10.successfactors.com/odata/v2/EmpJob(seqNumber=1L,startDate=datetime\'2021-08-29T00:00:00\',userId=\'213668\')?$format=json';
const result = interpolateUrlPathParams(url, [], {}, { raw: true });
expect(result).toEqual(url);
});
it('should replace only real OData path params and preserve datetime colons', () => {
const url
= 'https://example.com/odata/v2/EmpJob(seqNumber=:seqNumber,startDate=datetime\'2021-08-29T00:00:00\',userId=\':userId\')?$format=json';
const params = [
{ name: 'seqNumber', type: 'path', enabled: true, value: '1L' },
{ name: 'userId', type: 'path', enabled: true, value: '213668' }
];
const result = interpolateUrlPathParams(url, params, {}, { raw: true });
expect(result).toEqual(
'https://example.com/odata/v2/EmpJob(seqNumber=1L,startDate=datetime\'2021-08-29T00:00:00\',userId=\'213668\')?$format=json'
);
});
});