Skip to content

Commit 6612f06

Browse files
authored
Merge pull request #18 from arpitkuriyal/main
Done normalizing the OutputFormat
2 parents d4b0815 + 0200144 commit 6612f06

File tree

9 files changed

+883
-355
lines changed

9 files changed

+883
-355
lines changed

package-lock.json

Lines changed: 378 additions & 341 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"vitest": "*"
3838
},
3939
"dependencies": {
40-
"@hyperjump/json-schema": "^1.14.1"
40+
"@hyperjump/browser": "^1.3.1",
41+
"@hyperjump/json-schema": "^1.16.0"
4142
}
4243
}

src/index.d.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,45 @@
1-
export const hello: string;
1+
export const betterJsonSchemaErrors: (
2+
instance: Json,
3+
schema: SchemaObject,
4+
errorOutput: OutputFormat
5+
) => Promise<BetterJsonSchemaErrors>;
6+
7+
export type BetterJsonSchemaErrors = {
8+
errors: ErrorObject[];
9+
};
10+
11+
export type ErrorObject = {
12+
schemaLocation: string;
13+
instanceLocation: string;
14+
message: string;
15+
};
16+
17+
export type Json = string | number | boolean | null | JsonObject | Json[];
18+
export type JsonObject = {
19+
[property: string]: Json;
20+
};
21+
22+
export type SchemaFragment = string | number | boolean | null | SchemaObject | SchemaFragment[];
23+
export type SchemaObject = {
24+
[keyword: string]: SchemaFragment;
25+
};
26+
27+
export type OutputFormat = {
28+
valid: boolean;
29+
errors: OutputUnit[];
30+
};
31+
32+
export type OutputUnit = {
33+
valid?: boolean;
34+
absoluteKeywordLocation?: string;
35+
keywordLocation?: string;
36+
instanceLocation: string;
37+
error?: string;
38+
errors?: OutputUnit[];
39+
};
40+
41+
export type NormalizedError = {
42+
valid: false;
43+
absoluteKeywordLocation: string;
44+
instanceLocation: string;
45+
};

src/index.js

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
1+
import { normalizeOutputFormat } from "./normalizeOutputFormat/normalizeOutput.js";
2+
13
/**
2-
* @import * as API from "./index.d.ts"
4+
* @import {betterJsonSchemaErrors} from "./index.d.ts"
35
*/
46

5-
/** @type API.hello */
6-
export const hello = "world";
7+
/** @type betterJsonSchemaErrors */
8+
export async function betterJsonSchemaErrors(instance, schema, errorOutput) {
9+
const normalizedErrors = await normalizeOutputFormat(errorOutput, schema);
10+
11+
const errors = [];
12+
for (const error of normalizedErrors) {
13+
errors.push({
14+
message: "The instance should be at least 3 characters",
15+
instanceLocation: error.instanceLocation,
16+
schemaLocation: error.absoluteKeywordLocation
17+
});
18+
}
19+
20+
return { errors };
21+
}

src/index.test.js

Lines changed: 0 additions & 8 deletions
This file was deleted.

src/index.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { describe, test } from "vitest";
2+
3+
describe("Better JSON Schema Errors", () => {
4+
test("greeting", () => {
5+
});
6+
});
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import * as Browser from "@hyperjump/browser";
2+
import { registerSchema, unregisterSchema } from "@hyperjump/json-schema/draft-2020-12";
3+
import { getSchema } from "@hyperjump/json-schema/experimental";
4+
import { pointerSegments } from "@hyperjump/json-pointer";
5+
import { randomUUID } from "crypto";
6+
7+
/**
8+
* @import { OutputFormat, OutputUnit, NormalizedError, SchemaObject} from "../index.d.ts";
9+
* @import { SchemaDocument } from "@hyperjump/json-schema/experimental";
10+
* @import { Browser as BrowserType } from "@hyperjump/browser";
11+
*/
12+
13+
/**
14+
* @param {OutputFormat} errorOutput
15+
* @param {SchemaObject} schema
16+
* @returns {Promise<NormalizedError[]>}
17+
*/
18+
export async function normalizeOutputFormat(errorOutput, schema) {
19+
/** @type {NormalizedError[]} */
20+
const output = [];
21+
22+
if (!errorOutput || errorOutput.valid !== false) {
23+
throw new Error("error Output must follow Draft 2019-09");
24+
}
25+
26+
const keywords = new Set([
27+
"type", "minLength", "maxLength", "minimum", "maximum", "format", "pattern",
28+
"enum", "const", "required", "items", "properties", "allOf", "anyOf", "oneOf",
29+
"not", "contains", "uniqueItems", "additionalProperties", "minItems", "maxItems",
30+
"minProperties", "maxProperties", "dependentRequired", "dependencies"
31+
]);
32+
33+
/** @type {(errorOutput: OutputUnit) => Promise<void>} */
34+
async function collectErrors(error) {
35+
if (error.valid) return;
36+
37+
if (!("instanceLocation" in error) || !("absoluteKeywordLocation" in error || "keywordLocation" in error)) {
38+
throw new Error("error Output must follow Draft 2019-09");
39+
}
40+
41+
const absoluteKeywordLocation = error.absoluteKeywordLocation
42+
?? await toAbsoluteKeywordLocation(schema, /** @type string */ (error.keywordLocation));
43+
44+
const fragment = absoluteKeywordLocation.split("#")[1];
45+
const lastSegment = fragment.split("/").filter(Boolean).pop();
46+
47+
// make a check here to remove the schemaLocation.
48+
if (lastSegment && keywords.has(lastSegment)) {
49+
output.push({
50+
valid: false,
51+
absoluteKeywordLocation,
52+
instanceLocation: normalizeInstanceLocation(error.instanceLocation)
53+
});
54+
}
55+
56+
if (error.errors) {
57+
for (const nestedError of error.errors) {
58+
await collectErrors(nestedError); // Recursive
59+
}
60+
}
61+
}
62+
63+
if (!errorOutput.errors) {
64+
throw new Error("error Output must follow Draft 2019-09");
65+
}
66+
67+
for (const err of errorOutput.errors) {
68+
await collectErrors(err);
69+
}
70+
71+
return output;
72+
}
73+
74+
/** @type {(location: string) => string} */
75+
function normalizeInstanceLocation(location) {
76+
return location.startsWith("/") || location === "" ? `#${location}` : location;
77+
}
78+
79+
/**
80+
* Convert keywordLocation to absoluteKeywordLocation
81+
* @param {SchemaObject} schema
82+
* @param {string} keywordLocation
83+
* @returns {Promise<string>}
84+
*/
85+
export async function toAbsoluteKeywordLocation(schema, keywordLocation) {
86+
const uri = `urn:uuid:${randomUUID()}`;
87+
try {
88+
registerSchema(schema, uri);
89+
90+
let browser = await getSchema(uri);
91+
for (const segment of pointerSegments(keywordLocation)) {
92+
browser = /** @type BrowserType<SchemaDocument> */ (await Browser.step(segment, browser));
93+
}
94+
95+
return `${browser.document.baseUri}#${browser.cursor}`;
96+
} finally {
97+
unregisterSchema(uri);
98+
}
99+
}

0 commit comments

Comments
 (0)