Skip to content

Commit

Permalink
refactor: move parseJsonWithSchema to utils package (@fehmer)
Browse files Browse the repository at this point in the history
  • Loading branch information
fehmer committed Dec 12, 2024
1 parent 5454414 commit 5e5f574
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 26 deletions.
21 changes: 0 additions & 21 deletions frontend/src/ts/utils/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
Mode2,
PersonalBests,
} from "@monkeytype/contracts/schemas/shared";
import { ZodError, ZodSchema } from "zod";
import {
CustomTextDataWithTextLen,
Result,
Expand Down Expand Up @@ -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;
Expand Down
6 changes: 2 additions & 4 deletions frontend/src/ts/utils/url-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(
Expand Down
20 changes: 20 additions & 0 deletions packages/funbox/__test__/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 42 additions & 0 deletions packages/util/__test__/json.spec.ts
Original file line number Diff line number Diff line change
@@ -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`
)
);
});
});
});
3 changes: 2 additions & 1 deletion packages/util/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
".": {
Expand Down
27 changes: 27 additions & 0 deletions packages/util/src/json.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 5e5f574

Please sign in to comment.