mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-10-06 05:26:54 +08:00
fix: sanitize result filters before storing in LS (@fehmer) (#6583)
This commit is contained in:
parent
19930a9079
commit
1cada77ea8
4 changed files with 118 additions and 40 deletions
|
@ -1,4 +1,10 @@
|
|||
import { deepClone, getErrorMessage, isObject } from "../../src/ts/utils/misc";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
deepClone,
|
||||
getErrorMessage,
|
||||
isObject,
|
||||
sanitize,
|
||||
} from "../../src/ts/utils/misc";
|
||||
import {
|
||||
getLanguageDisplayString,
|
||||
removeLanguageSize,
|
||||
|
@ -224,4 +230,56 @@ describe("misc.ts", () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
describe("sanitize function", () => {
|
||||
const schema = z.object({
|
||||
name: z.string(),
|
||||
age: z.number().positive(),
|
||||
tags: z.array(z.string()),
|
||||
});
|
||||
|
||||
it("should return the same object if it is valid", () => {
|
||||
const obj = { name: "Alice", age: 30, tags: ["developer", "coder"] };
|
||||
expect(sanitize(schema, obj)).toEqual(obj);
|
||||
});
|
||||
|
||||
it("should remove properties with invalid values", () => {
|
||||
const obj = { name: "Alice", age: -5, tags: ["developer", "coder"] };
|
||||
expect(sanitize(schema, obj)).toEqual({
|
||||
name: "Alice",
|
||||
tags: ["developer", "coder"],
|
||||
age: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it("should remove invalid array elements", () => {
|
||||
const obj = {
|
||||
name: "Alice",
|
||||
age: 30,
|
||||
tags: ["developer", 123, "coder"] as any,
|
||||
};
|
||||
expect(sanitize(schema, obj)).toEqual({
|
||||
name: "Alice",
|
||||
age: 30,
|
||||
tags: ["developer", "coder"],
|
||||
});
|
||||
});
|
||||
|
||||
it("should remove entire property if all array elements are invalid", () => {
|
||||
const obj = { name: "Alice", age: 30, tags: [123, 456] as any };
|
||||
expect(sanitize(schema, obj)).toEqual({
|
||||
name: "Alice",
|
||||
age: 30,
|
||||
tags: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it("should remove object properties if they are invalid", () => {
|
||||
const obj = { name: 123 as any, age: 30, tags: ["developer", "coder"] };
|
||||
expect(sanitize(schema, obj)).toEqual({
|
||||
age: 30,
|
||||
tags: ["developer", "coder"],
|
||||
name: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -55,7 +55,9 @@ const resultFiltersLS = new LocalStorageWithSchema({
|
|||
if (!Misc.isObject(unknown)) {
|
||||
return defaultResultFilters;
|
||||
}
|
||||
return mergeWithDefaultFilters(unknown as ResultFilters);
|
||||
return mergeWithDefaultFilters(
|
||||
Misc.sanitize(ResultFiltersSchema, unknown as ResultFilters)
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -89,6 +91,7 @@ function save(): void {
|
|||
export async function load(): Promise<void> {
|
||||
try {
|
||||
filters = resultFiltersLS.get();
|
||||
console.log("###", { filters });
|
||||
|
||||
const newTags: Record<string, boolean> = { none: false };
|
||||
Object.keys(defaultResultFilters.tags).forEach((tag) => {
|
||||
|
@ -889,7 +892,8 @@ $(".group.presetFilterButtons .filterBtns").on(
|
|||
);
|
||||
|
||||
function verifyResultFiltersStructure(filterIn: ResultFilters): ResultFilters {
|
||||
const filter = Misc.deepClone(filterIn);
|
||||
const filter = Misc.sanitize(ResultFiltersSchema, Misc.deepClone(filterIn));
|
||||
|
||||
Object.entries(defaultResultFilters).forEach((entry) => {
|
||||
const key = entry[0] as ResultFiltersGroup;
|
||||
const value = entry[1];
|
||||
|
|
|
@ -4,7 +4,7 @@ import {
|
|||
PartialConfig,
|
||||
FunboxName,
|
||||
} from "@monkeytype/contracts/schemas/configs";
|
||||
import { typedKeys } from "./misc";
|
||||
import { sanitize, typedKeys } from "./misc";
|
||||
import * as ConfigSchemas from "@monkeytype/contracts/schemas/configs";
|
||||
import { getDefaultConfig } from "../constants/default-config";
|
||||
/**
|
||||
|
@ -33,42 +33,7 @@ function mergeWithDefaultConfig(config: PartialConfig): Config {
|
|||
function sanitizeConfig(
|
||||
config: ConfigSchemas.PartialConfig
|
||||
): ConfigSchemas.PartialConfig {
|
||||
const validate = ConfigSchemas.PartialConfigSchema.safeParse(config);
|
||||
|
||||
if (validate.success) {
|
||||
return config;
|
||||
}
|
||||
|
||||
const errors: Map<string, number[] | undefined> = new Map();
|
||||
for (const error of validate.error.errors) {
|
||||
const element = error.path[0] as string;
|
||||
let val = errors.get(element);
|
||||
if (typeof error.path[1] === "number") {
|
||||
val = [...(val ?? []), error.path[1]];
|
||||
}
|
||||
errors.set(element, val);
|
||||
}
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(config).map(([key, value]) => {
|
||||
if (!errors.has(key)) {
|
||||
return [key, value];
|
||||
}
|
||||
|
||||
const error = errors.get(key);
|
||||
|
||||
if (
|
||||
Array.isArray(value) &&
|
||||
error !== undefined && //error is not on the array itself
|
||||
error.length < value.length //not all items in the array are invalid
|
||||
) {
|
||||
//some items of the array are invalid
|
||||
return [key, value.filter((_element, index) => !error.includes(index))];
|
||||
} else {
|
||||
return [key, undefined];
|
||||
}
|
||||
})
|
||||
) as ConfigSchemas.PartialConfig;
|
||||
return sanitize(ConfigSchemas.PartialConfigSchema, config);
|
||||
}
|
||||
|
||||
export function replaceLegacyValues(
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
CustomTextDataWithTextLen,
|
||||
Result,
|
||||
} from "@monkeytype/contracts/schemas/results";
|
||||
import { z } from "zod";
|
||||
|
||||
export function whorf(speed: number, wordlen: number): number {
|
||||
return Math.min(
|
||||
|
@ -713,4 +714,54 @@ export function promiseWithResolvers<T = void>(): {
|
|||
return { resolve, reject, promise };
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize object. Remove invalid values based on the schema.
|
||||
* @param schema zod schema
|
||||
* @param obj object
|
||||
* @returns sanitized object
|
||||
*/
|
||||
export function sanitize<T extends z.ZodTypeAny>(
|
||||
schema: T,
|
||||
obj: z.infer<T>
|
||||
): z.infer<T> {
|
||||
const validate = schema.safeParse(obj);
|
||||
|
||||
if (validate.success) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
return obj;
|
||||
}
|
||||
|
||||
const errors: Map<string, number[] | undefined> = new Map();
|
||||
for (const error of validate.error.errors) {
|
||||
const element = error.path[0] as string;
|
||||
let val = errors.get(element);
|
||||
if (typeof error.path[1] === "number") {
|
||||
val = [...(val ?? []), error.path[1]];
|
||||
}
|
||||
errors.set(element, val);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj).map(([key, value]) => {
|
||||
if (!errors.has(key)) {
|
||||
return [key, value];
|
||||
}
|
||||
|
||||
const error = errors.get(key);
|
||||
|
||||
if (
|
||||
Array.isArray(value) &&
|
||||
error !== undefined && //error is not on the array itself
|
||||
error.length < value.length //not all items in the array are invalid
|
||||
) {
|
||||
//some items of the array are invalid
|
||||
return [key, value.filter((_element, index) => !error.includes(index))];
|
||||
} else {
|
||||
return [key, undefined];
|
||||
}
|
||||
})
|
||||
) as z.infer<T>;
|
||||
}
|
||||
|
||||
// DO NOT ALTER GLOBAL OBJECTSONSTRUCTOR, IT WILL BREAK RESULT HASHES
|
||||
|
|
Loading…
Add table
Reference in a new issue