Skip to content

Commit 8d35116

Browse files
authored
fix: Add caps for some numbers (#95)
* fix: Add caps for some numbers * fix: Updates based on feedback
1 parent 5a1428f commit 8d35116

File tree

2 files changed

+101
-2
lines changed

2 files changed

+101
-2
lines changed

app/src/core/Utilities.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,22 @@ export const ObjectKeyAvoidTermList = new Set([
3333
"__lookupSetter__",
3434
]);
3535

36+
/**
37+
* Maximum safe integer value for .NET Int32 compatibility.
38+
* Using 2,000,000,000 as a round number that is obviously a cap,
39+
* well under Int32.MaxValue (2,147,483,647) to ensure compatibility
40+
* with downstream JSON consumers like .NET.
41+
*/
42+
export const MAX_JSON_SAFE_INTEGER = 2_000_000_000;
43+
44+
/**
45+
* Minimum safe integer value for .NET Int32 compatibility.
46+
* Using -2,000,000,000 as a round number that is obviously a cap,
47+
* well above Int32.MinValue (-2,147,483,648) to ensure compatibility
48+
* with downstream JSON consumers like .NET.
49+
*/
50+
export const MIN_JSON_SAFE_INTEGER = -2_000_000_000;
51+
3652
export default class Utilities {
3753
static _isDebug?: boolean;
3854
static _isAppSim?: boolean;
@@ -2067,4 +2083,62 @@ export default class Utilities {
20672083
static isNullOrUndefined<T>(object: T | undefined | null): object is T {
20682084
return (object as T) === undefined || (object as T) === null;
20692085
}
2086+
2087+
/**
2088+
* Clamps a number for broader compatibility (e.g., .NET).
2089+
* Returns undefined if the input is undefined.
2090+
*/
2091+
static capNumberForJson(value: number | undefined): number | undefined {
2092+
if (value === undefined) {
2093+
return undefined;
2094+
}
2095+
return Math.max(MIN_JSON_SAFE_INTEGER, Math.min(value, MAX_JSON_SAFE_INTEGER));
2096+
}
2097+
2098+
/**
2099+
* Deep clones a feature sets object, clamping all numeric values to the safe Int32 range.
2100+
* This ensures compatibility with .NET which has Int32 limits.
2101+
* Preserves undefined values for individual measures (undefined in = undefined out).
2102+
*/
2103+
static capFeatureSetsForJson(
2104+
featureSets: { [setName: string]: { [measureName: string]: number | undefined } | undefined } | undefined
2105+
): { [setName: string]: { [measureName: string]: number | undefined } | undefined } | undefined {
2106+
if (!featureSets) {
2107+
return undefined;
2108+
}
2109+
2110+
const result: { [setName: string]: { [measureName: string]: number | undefined } | undefined } = {};
2111+
2112+
for (const setName in featureSets) {
2113+
const setVal = featureSets[setName];
2114+
if (setVal) {
2115+
const newSetVal: { [measureName: string]: number | undefined } = {};
2116+
for (const measureName in setVal) {
2117+
newSetVal[measureName] = Utilities.capNumberForJson(setVal[measureName]);
2118+
}
2119+
result[setName] = newSetVal;
2120+
}
2121+
}
2122+
2123+
return result;
2124+
}
2125+
2126+
/**
2127+
* Clamps a data value for broader compatibility (e.g., .NET).
2128+
* Handles the IInfoItemData.d type: string | boolean | number | number[] | undefined
2129+
*/
2130+
static capDataValueForJson(
2131+
value: string | boolean | number | number[] | undefined
2132+
): string | boolean | number | number[] | undefined {
2133+
if (value === undefined || typeof value === "string" || typeof value === "boolean") {
2134+
return value;
2135+
}
2136+
2137+
if (typeof value === "number") {
2138+
return Math.max(MIN_JSON_SAFE_INTEGER, Math.min(value, MAX_JSON_SAFE_INTEGER));
2139+
}
2140+
2141+
// It's a number array
2142+
return value.map((v) => Math.max(MIN_JSON_SAFE_INTEGER, Math.min(v, MAX_JSON_SAFE_INTEGER)));
2143+
}
20702144
}

app/src/info/ProjectInfoSet.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -993,14 +993,39 @@ export default class ProjectInfoSet {
993993
const dataObj = this.items[i].dataObject;
994994

995995
if (!isIndexOnly || this.shouldIncludeInIndex(dataObj)) {
996-
items.push(dataObj);
996+
// Check if we need to cap numeric values for .NET Int32 compatibility
997+
const hasFeatureSetsToCap = dataObj.fs && Object.keys(dataObj.fs).length > 0;
998+
const needsCapping = hasFeatureSetsToCap || typeof dataObj.d === "number" || Array.isArray(dataObj.d);
999+
1000+
if (needsCapping) {
1001+
const cappedDataObj: IInfoItemData = { ...dataObj };
1002+
if (hasFeatureSetsToCap) {
1003+
cappedDataObj.fs = Utilities.capFeatureSetsForJson(dataObj.fs);
1004+
}
1005+
if (typeof dataObj.d === "number" || Array.isArray(dataObj.d)) {
1006+
cappedDataObj.d = Utilities.capDataValueForJson(dataObj.d);
1007+
}
1008+
items.push(cappedDataObj);
1009+
} else {
1010+
items.push(dataObj);
1011+
}
9971012
}
9981013
}
9991014

10001015
Utilities.encodeObjectWithSequentialRunLengthEncodeUsingNegative(this.contentIndex.data.trie);
10011016

1017+
// Cap featureSets in info for .NET Int32 compatibility.
1018+
// Note: Other numeric fields in IProjectInfo (errorCount, warningCount, etc.) are per-project
1019+
// counts that won't exceed Int32. Only featureSets can contain very large aggregated values.
1020+
const cappedInfo: IProjectInfo = this.info
1021+
? {
1022+
...this.info,
1023+
featureSets: Utilities.capFeatureSetsForJson(this.info.featureSets),
1024+
}
1025+
: {};
1026+
10021027
return {
1003-
info: this.info,
1028+
info: cappedInfo,
10041029
items: items,
10051030
index: this.contentIndex.data,
10061031
generatorName: constants.name,

0 commit comments

Comments
 (0)