mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-11-08 05:03:39 +08:00
refactor: move parseJsonWithSchema to utils package (@fehmer) (#6109)
This commit is contained in:
parent
4baae8fe37
commit
353fc14b4e
7 changed files with 96 additions and 26 deletions
|
|
@ -7,7 +7,6 @@ import {
|
|||
Mode2,
|
||||
PersonalBests,
|
||||
} from "@monkeytype/contracts/schemas/shared";
|
||||
import { ZodError, ZodSchema } from "zod";
|
||||
import {
|
||||
CustomTextDataWithTextLen,
|
||||
Result,
|
||||
|
|
@ -655,26 +654,6 @@ export function isObject(obj: unknown): obj is Record<string, unknown> {
|
|||
return typeof obj === "object" && !Array.isArray(obj) && obj !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a JSON string into an object and validate it against a schema
|
||||
* @param input JSON string
|
||||
* @param schema Zod schema to validate the JSON against
|
||||
* @returns The parsed JSON object
|
||||
*/
|
||||
export function parseJsonWithSchema<T>(input: string, schema: ZodSchema<T>): T {
|
||||
try {
|
||||
const jsonParsed = JSON.parse(input) as unknown;
|
||||
const zodParsed = schema.parse(jsonParsed);
|
||||
return zodParsed;
|
||||
} catch (error) {
|
||||
if (error instanceof ZodError) {
|
||||
throw new Error(error.errors.map((err) => err.message).join("\n"));
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function deepClone<T>(obj: T[]): T[];
|
||||
export function deepClone<T extends object>(obj: T): T;
|
||||
export function deepClone<T>(obj: T): T;
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import {
|
|||
Difficulty,
|
||||
} from "@monkeytype/contracts/schemas/configs";
|
||||
import { z } from "zod";
|
||||
import { parseWithSchema as parseJsonWithSchema } from "@monkeytype/util/json";
|
||||
|
||||
export async function linkDiscord(hashOverride: string): Promise<void> {
|
||||
if (!hashOverride) return;
|
||||
|
|
@ -78,10 +79,7 @@ export function loadCustomThemeFromUrl(getOverride?: string): void {
|
|||
|
||||
let decoded: z.infer<typeof customThemeUrlDataSchema>;
|
||||
try {
|
||||
decoded = Misc.parseJsonWithSchema(
|
||||
atob(getValue),
|
||||
customThemeUrlDataSchema
|
||||
);
|
||||
decoded = parseJsonWithSchema(atob(getValue), customThemeUrlDataSchema);
|
||||
} catch (e) {
|
||||
console.log("Custom theme URL decoding failed", e);
|
||||
Notifications.add(
|
||||
|
|
|
|||
20
packages/funbox/__test__/utils.spec.ts
Normal file
20
packages/funbox/__test__/utils.spec.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import * as Util from "../src/util";
|
||||
|
||||
describe("util", () => {
|
||||
describe("stringToFunboxNames", () => {
|
||||
it("should get single funbox", () => {
|
||||
expect(Util.stringToFunboxNames("58008")).toEqual(["58008"]);
|
||||
});
|
||||
it("should fail for unknown funbox name", () => {
|
||||
expect(() => Util.stringToFunboxNames("unknown")).toThrowError(
|
||||
new Error("Invalid funbox name: unknown")
|
||||
);
|
||||
});
|
||||
it("should split multiple funboxes by hash", () => {
|
||||
expect(Util.stringToFunboxNames("58008#choo_choo")).toEqual([
|
||||
"58008",
|
||||
"choo_choo",
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
42
packages/util/__test__/json.spec.ts
Normal file
42
packages/util/__test__/json.spec.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import { parseWithSchema } from "../src/json";
|
||||
import { z } from "zod";
|
||||
|
||||
describe("json", () => {
|
||||
describe("parseWithSchema", () => {
|
||||
const schema = z.object({
|
||||
test: z.boolean().optional(),
|
||||
name: z.string(),
|
||||
nested: z.object({ foo: z.string() }).strict().optional(),
|
||||
});
|
||||
it("should parse", () => {
|
||||
const json = `{
|
||||
"test":true,
|
||||
"name":"bob",
|
||||
"unknown":"unknown",
|
||||
"nested":{
|
||||
"foo":"bar"
|
||||
}
|
||||
}`;
|
||||
|
||||
expect(parseWithSchema(json, schema)).toStrictEqual({
|
||||
test: true,
|
||||
name: "bob",
|
||||
nested: { foo: "bar" },
|
||||
});
|
||||
});
|
||||
it("should fail with invalid schema", () => {
|
||||
const json = `{
|
||||
"test":"yes",
|
||||
"nested":{
|
||||
"foo":1
|
||||
}
|
||||
}`;
|
||||
|
||||
expect(() => parseWithSchema(json, schema)).toThrowError(
|
||||
new Error(
|
||||
`"test" Expected boolean, received string\n"name" Required\n"nested.foo" Expected string, received number`
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -18,7 +18,8 @@
|
|||
"madge": "8.0.0",
|
||||
"rimraf": "6.0.1",
|
||||
"typescript": "5.5.4",
|
||||
"vitest": "2.0.5"
|
||||
"vitest": "2.0.5",
|
||||
"zod": "3.23.8"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
|
|
|
|||
27
packages/util/src/json.ts
Normal file
27
packages/util/src/json.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { ZodError, ZodIssue, ZodSchema } from "zod";
|
||||
|
||||
/**
|
||||
* Parse a JSON string into an object and validate it against a schema
|
||||
* @param json JSON string
|
||||
* @param schema Zod schema to validate the JSON against
|
||||
* @returns The parsed JSON object
|
||||
*/
|
||||
export function parseWithSchema<T>(json: string, schema: ZodSchema<T>): T {
|
||||
try {
|
||||
const jsonParsed = JSON.parse(json) as unknown;
|
||||
const zodParsed = schema.parse(jsonParsed);
|
||||
return zodParsed;
|
||||
} catch (error) {
|
||||
if (error instanceof ZodError) {
|
||||
throw new Error(error.issues.map(prettyErrorMessage).join("\n"));
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function prettyErrorMessage(issue: ZodIssue | undefined): string {
|
||||
if (issue === undefined) return "";
|
||||
const path = issue.path.length > 0 ? `"${issue.path.join(".")}" ` : "";
|
||||
return `${path}${issue.message}`;
|
||||
}
|
||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
|
|
@ -614,6 +614,9 @@ importers:
|
|||
vitest:
|
||||
specifier: 2.0.5
|
||||
version: 2.0.5(@types/node@20.14.11)(happy-dom@15.10.2)(sass@1.70.0)(terser@5.31.3)
|
||||
zod:
|
||||
specifier: 3.23.8
|
||||
version: 3.23.8
|
||||
|
||||
packages:
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue