|
1 | 1 | import { normalizeOutputFormat } from "./normalizeOutputFormat/normalizeOutput.js";
|
| 2 | +import * as Schema from "@hyperjump/browser"; |
| 3 | +import { getSchema, getKeyword } from "@hyperjump/json-schema/experimental"; |
| 4 | +import * as Instance from "@hyperjump/json-pointer"; |
| 5 | +import leven from "leven"; |
2 | 6 |
|
3 | 7 | /**
|
4 |
| - * @import {betterJsonSchemaErrors} from "./index.d.ts" |
| 8 | + * @import { Browser } from "@hyperjump/browser"; |
| 9 | + * @import { SchemaDocument } from "@hyperjump/json-schema/experimental"; |
| 10 | + * @import { Json } from "@hyperjump/json-pointer"; |
| 11 | + * @import {betterJsonSchemaErrors, OutputUnit } from "./index.d.ts" |
5 | 12 | */
|
6 | 13 |
|
7 | 14 | /** @type betterJsonSchemaErrors */
|
8 |
| -export async function betterJsonSchemaErrors(instance, schema, errorOutput) { |
| 15 | +export async function betterJsonSchemaErrors(instance, errorOutput, schemaUri) { |
| 16 | + const schema = await getSchema(schemaUri); |
9 | 17 | const normalizedErrors = await normalizeOutputFormat(errorOutput, schema);
|
10 |
| - |
11 | 18 | const errors = [];
|
12 | 19 | for (const error of normalizedErrors) {
|
| 20 | + const keywordHandler = getKeyword(error.keyword); |
| 21 | + if (keywordHandler.simpleApplicator) { |
| 22 | + continue; |
| 23 | + } |
| 24 | + |
| 25 | + /** @type Browser<SchemaDocument> */ |
| 26 | + const schema = await getSchema(error.absoluteKeywordLocation); |
13 | 27 | errors.push({
|
14 |
| - message: "The instance should be at least 3 characters", |
| 28 | + message: getErrorMessage(error, schema, instance), |
15 | 29 | instanceLocation: error.instanceLocation,
|
16 | 30 | schemaLocation: error.absoluteKeywordLocation
|
17 | 31 | });
|
18 | 32 | }
|
19 | 33 |
|
20 | 34 | return { errors };
|
21 | 35 | }
|
| 36 | + |
| 37 | +/** @type (outputUnit: OutputUnit, schema: Browser<SchemaDocument>, instance: Json) => string */ |
| 38 | +const getErrorMessage = (outputUnit, schema, instance) => { |
| 39 | + if (outputUnit.keyword === "https://json-schema.org/keyword/minLength") { |
| 40 | + return `The instance should be at least ${Schema.value(schema)} characters`; |
| 41 | + } |
| 42 | + |
| 43 | + if (outputUnit.keyword === "https://json-schema.org/keyword/maxLength") { |
| 44 | + return `The instance should be atmost ${Schema.value(schema)} characters long.`; |
| 45 | + } |
| 46 | + |
| 47 | + if (outputUnit.keyword === "https://json-schema.org/keyword/type") { |
| 48 | + const pointer = outputUnit.instanceLocation.replace(/^#/, ""); |
| 49 | + const actualValue = Instance.get(pointer, instance); |
| 50 | + return `The instance should be of type "${Schema.value(schema)}" but found "${typeof actualValue}".`; |
| 51 | + } |
| 52 | + |
| 53 | + if (outputUnit.keyword === "https://json-schema.org/keyword/maximum") { |
| 54 | + return `The instance should be less than or equal to ${Schema.value(schema)}.`; |
| 55 | + } |
| 56 | + |
| 57 | + if (outputUnit.keyword === "https://json-schema.org/keyword/minimum") { |
| 58 | + return `The instance should be greater than or equal to ${Schema.value(schema)}.`; |
| 59 | + } |
| 60 | + |
| 61 | + if (outputUnit.keyword === "https://json-schema.org/keyword/exclusiveMaximum") { |
| 62 | + return `The instance should be less than ${Schema.value(schema)}.`; |
| 63 | + } |
| 64 | + |
| 65 | + if (outputUnit.keyword === "https://json-schema.org/keyword/exclusiveMinimum") { |
| 66 | + return `The instance should be greater than ${Schema.value(schema)}.`; |
| 67 | + } |
| 68 | + |
| 69 | + if (outputUnit.keyword === "https://json-schema.org/keyword/required") { |
| 70 | + /** @type {Set<string>} */ |
| 71 | + const required = new Set(Schema.value(schema)); |
| 72 | + const pointer = outputUnit.instanceLocation.replace(/^#/, ""); |
| 73 | + const object = /** @type Object */ (Instance.get(pointer, instance)); |
| 74 | + for (const propertyName of Object.keys(object)) { |
| 75 | + required.delete(propertyName); |
| 76 | + } |
| 77 | + |
| 78 | + return `"${outputUnit.instanceLocation}" is missing required property(s): ${[...required].join(", ")}.`; |
| 79 | + } |
| 80 | + |
| 81 | + if (outputUnit.keyword === "https://json-schema.org/keyword/multipleOf") { |
| 82 | + return `The instance should be of multiple of ${Schema.value(schema)}.`; |
| 83 | + } |
| 84 | + |
| 85 | + if (outputUnit.keyword === "https://json-schema.org/keyword/maxProperties") { |
| 86 | + return `The instance should have maximum ${Schema.value(schema)} properties.`; |
| 87 | + } |
| 88 | + |
| 89 | + if (outputUnit.keyword === "https://json-schema.org/keyword/minProperties") { |
| 90 | + return `The instance should have minimum ${Schema.value(schema)} properties.`; |
| 91 | + } |
| 92 | + |
| 93 | + if (outputUnit.keyword === "https://json-schema.org/keyword/const") { |
| 94 | + return `The instance should be equal to ${Schema.value(schema)}.`; |
| 95 | + } |
| 96 | + |
| 97 | + if (outputUnit.keyword === "https://json-schema.org/keyword/enum") { |
| 98 | + /** @type {Array<string>} */ |
| 99 | + const allowedValues = Schema.value(schema); |
| 100 | + const pointer = outputUnit.instanceLocation.replace(/^#/, ""); |
| 101 | + const currentValue = /** @type {string} */ (Instance.get(pointer, instance)); |
| 102 | + |
| 103 | + const bestMatch = allowedValues |
| 104 | + .map((value) => ({ |
| 105 | + value, |
| 106 | + weight: leven(value, currentValue) |
| 107 | + })) |
| 108 | + .sort((a, b) => a.weight - b.weight)[0]; |
| 109 | + |
| 110 | + let suggestion = ""; |
| 111 | + if ( |
| 112 | + allowedValues.length === 1 |
| 113 | + || (bestMatch && bestMatch.weight < bestMatch.value.length) |
| 114 | + ) { |
| 115 | + suggestion = ` Did you mean "${bestMatch.value}"?`; |
| 116 | + return `Unexpected value "${currentValue}". ${suggestion}`; |
| 117 | + } |
| 118 | + |
| 119 | + return `Unexpected value "${currentValue}". Expected one of: ${allowedValues.join(",")}.`; |
| 120 | + } |
| 121 | + throw Error("TODO: Error message not implemented"); |
| 122 | +}; |
0 commit comments