diff --git a/.gitignore b/.gitignore
index 07ab3ad..f72544f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@ node_modules
dist
/public
coverage
+cjs
diff --git a/package.json b/package.json
index 094cc44..3563ac7 100644
--- a/package.json
+++ b/package.json
@@ -50,7 +50,7 @@
"@types/json-schema": "^7.0.12",
"@types/node": "^20.4.2",
"json-schema": "^0.2.3",
- "json-schema-library": "^8.0.0"
+ "json-schema-library": "^9.0.0"
},
"optionalDependencies": {
"@codemirror/lang-json": "^6.0.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 6c1018d..d5e8568 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -36,8 +36,8 @@ dependencies:
specifier: ^0.2.3
version: 0.2.3
json-schema-library:
- specifier: ^8.0.0
- version: 8.0.0
+ specifier: ^9.0.0
+ version: 9.0.0
optionalDependencies:
'@codemirror/lang-json':
@@ -1938,8 +1938,8 @@ packages:
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
dev: true
- /json-schema-library@8.0.0:
- resolution: {integrity: sha512-qqsEdyhuA68YHzuWNGrOk9ViknRKw1NfIbhT9wQ0z6l5cpfuYoqKRkbu8tgHAXjahmLEkpNdGwHq+gCgIrMYeA==}
+ /json-schema-library@9.0.0:
+ resolution: {integrity: sha512-X0eC3rcC1kAoR5YXG8h6X205PMjlBzmWZWpRCRI2taESZLYS2TglKiY8u/RuuMWWDaE1tgSkcpees5G2swOQug==}
dependencies:
'@sagold/json-pointer': 5.1.1
'@sagold/json-query': 6.1.1
diff --git a/src/__tests__/json-validation.spec.ts b/src/__tests__/json-validation.spec.ts
index afcfa32..7997c0a 100644
--- a/src/__tests__/json-validation.spec.ts
+++ b/src/__tests__/json-validation.spec.ts
@@ -11,21 +11,25 @@ const getErrors = (jsonString: string, schema?: JSONSchema7) => {
const view = new EditorView({ doc: jsonString, extensions: [json()] });
return new JSONValidation(schema || testSchema).doValidation(view);
};
+
+const common = {
+ severity: "error" as Diagnostic["severity"],
+ source: "json-schema",
+};
+
const expectErrors = (
jsonString: string,
errors: [from: number, to: number, message: string][],
schema?: JSONSchema7
) => {
- expect(getErrors(jsonString, schema)).toEqual(
+ const filteredErrors = getErrors(jsonString, schema).map(
+ ({ renderMessage, ...error }) => error
+ );
+ expect(filteredErrors).toEqual(
errors.map(([from, to, message]) => ({ ...common, from, to, message }))
);
};
-const common = {
- severity: "error" as Diagnostic["severity"],
- source: "json-schema",
-};
-
describe("json-validation", () => {
it("should provide range for a value error", () => {
expectErrors('{"foo": 123}', [
@@ -34,7 +38,7 @@ describe("json-validation", () => {
});
it("should provide range for an unknown key error", () => {
expectErrors('{"foo": "example", "bar": 123}', [
- [19, 24, "Additional property `bar` in `#` is not allowed"],
+ [19, 24, "Additional property `bar` is not allowed"],
]);
});
it("should not handle invalid json", () => {
@@ -46,7 +50,7 @@ describe("json-validation", () => {
"foo": "example",
"bar": "something else"
}`,
- [[32, 37, "Additional property `bar` in `#` is not allowed"]]
+ [[32, 37, "Additional property `bar` is not allowed"]]
);
});
it("should provide formatted error message for oneOf fields with more than 2 items", () => {
@@ -65,7 +69,7 @@ describe("json-validation", () => {
"foo": "example",
"oneOfEg2": 123
}`,
- [[44, 47, 'Expected one of `"string"` or `"array"`']],
+ [[44, 47, "Expected one of string
or array
"]],
testSchema2
);
});
diff --git a/src/json-completion.ts b/src/json-completion.ts
index fb56f3c..8cbfc8f 100644
--- a/src/json-completion.ts
+++ b/src/json-completion.ts
@@ -17,10 +17,9 @@ import {
stripSurroundingQuotes,
getNodeAtPosition,
} from "./utils/node";
-import { Draft07, JsonError } from "json-schema-library";
+import { Draft07, JsonError, isJsonError } from "json-schema-library";
import { jsonPointerForPosition } from "./utils/jsonPointers";
import { TOKENS } from "./constants";
-import getSchema from "./utils/schema-lib/getSchema";
function json5PropertyInsertSnippet(rawWord: string, value: string) {
if (rawWord.startsWith('"')) {
@@ -683,7 +682,10 @@ export class JSONCompletion {
): JSONSchema7Definition[] {
const draft = new Draft07(this.schema);
let pointer = jsonPointerForPosition(ctx.state, ctx.pos);
- let subSchema = getSchema(draft, pointer);
+ let subSchema = draft.getSchema({ pointer });
+ if (isJsonError(subSchema)) {
+ subSchema = subSchema.data?.schema;
+ }
// if we don't have a schema for the current pointer, try the parent pointer
if (
!subSchema ||
@@ -692,7 +694,7 @@ export class JSONCompletion {
subSchema.type === "undefined"
) {
pointer = pointer.replace(/\/[^/]*$/, "/");
- subSchema = getSchema(draft, pointer);
+ subSchema = draft.getSchema({ pointer });
}
debug.log("xxx", "pointer..", JSON.stringify(pointer));
@@ -703,8 +705,7 @@ export class JSONCompletion {
}
// const subSchema = new Draft07(this.schema).getSchema(pointer);
debug.log("xxx", "subSchema..", subSchema);
-
- if (this.isJsonError(subSchema)) {
+ if (!subSchema) {
return [];
}
@@ -730,10 +731,6 @@ export class JSONCompletion {
return [subSchema as JSONSchema7];
}
- isJsonError(d: JSONSchema7 | JsonError): d is JsonError {
- return d.type === "error";
- }
-
private expandSchemaProperty(
property: JSONSchema7Definition,
schema: JSONSchema7
diff --git a/src/json-hover.ts b/src/json-hover.ts
index df924ba..e52be49 100644
--- a/src/json-hover.ts
+++ b/src/json-hover.ts
@@ -1,10 +1,14 @@
import { type EditorView, Tooltip } from "@codemirror/view";
-import { type Draft, Draft04, JsonSchema } from "json-schema-library";
+import {
+ type Draft,
+ Draft04,
+ JsonSchema,
+ isJsonError,
+} from "json-schema-library";
import type { JSONSchema7 } from "json-schema";
import { JSONMode, jsonPointerForPosition } from "./utils/jsonPointers";
import { joinWithOr } from "./utils/formatting";
-import getSchema from "./utils/schema-lib/getSchema";
import { debug } from "./utils/debug";
import { Side } from "./types";
import { el } from "./utils/dom";
@@ -56,8 +60,11 @@ function formatComplexType(
export class JSONHover {
private schema: Draft;
- public constructor(schema: JSONSchema7, private opts?: HoverOptions) {
- this.schema = new Draft04(schema);
+ public constructor(
+ private _schema: JSONSchema7,
+ private opts?: HoverOptions
+ ) {
+ this.schema = new Draft04(_schema);
this.opts = {
parser: JSON.parse,
...this.opts,
@@ -80,12 +87,19 @@ export class JSONHover {
return null;
}
// if the data is valid, we can infer a type for complex types
- let subSchema = getSchema(this.schema, pointer, data);
- if (subSchema.type === "error" && data !== undefined) {
- // if the data is invalid, we won't get the type - try again without the data
- subSchema = getSchema(this.schema, pointer, undefined);
- if (subSchema.type === "error") {
- return { pointer };
+ let subSchema = this.schema.getSchema({
+ pointer,
+ data,
+ schema: this._schema,
+ withSchemaWarning: true,
+ });
+ if (isJsonError(subSchema)) {
+ console.log("subschema", subSchema.data);
+
+ if (subSchema?.data.schema["$ref"]) {
+ subSchema = this.schema.resolveRef(subSchema);
+ } else {
+ subSchema = subSchema?.data.schema;
}
}
@@ -101,7 +115,15 @@ export class JSONHover {
text: message,
}),
el("div", { class: "cm6-json-schema-hover--code-wrapper" }, [
- el("code", { class: "cm6-json-schema-hover--code", text: typeInfo }),
+ typeInfo.includes("")
+ ? el("div", {
+ class: "cm6-json-schema-hover--code",
+ inner: typeInfo,
+ })
+ : el("code", {
+ class: "cm6-json-schema-hover--code",
+ text: typeInfo,
+ }),
]),
]);
}
@@ -117,6 +139,7 @@ export class JSONHover {
let message = null;
const { schema } = data;
+ console.log(schema, data);
if (schema.oneOf) {
typeInfo = formatComplexType(schema, "oneOf", draft);
}
@@ -131,6 +154,15 @@ export class JSONHover {
? joinWithOr(schema.type)
: schema.type;
}
+ if (schema.enum) {
+ typeInfo = `enum
: ${joinWithOr(schema.enum)}`;
+ }
+ if (schema.format) {
+ typeInfo += ` format
: ${schema.format}`;
+ }
+ if (schema.pattern) {
+ typeInfo += ` pattern
: ${schema.pattern}`;
+ }
if (schema.description) {
message = schema.description;
}
diff --git a/src/json-validation.ts b/src/json-validation.ts
index bd84391..a2f7028 100644
--- a/src/json-validation.ts
+++ b/src/json-validation.ts
@@ -6,6 +6,7 @@ import { joinWithOr } from "./utils/formatting";
import { JSONPointerData } from "./types";
import { parseJSONDocumentState } from "./utils/parseJSONDocument";
import { RequiredPick } from "./types";
+import { el } from "./utils/dom";
// return an object path that matches with the json-source-map pointer
const getErrorPath = (error: JsonError): string => {
@@ -59,26 +60,33 @@ export class JSONValidation {
this.schema = new Draft04(schema);
}
private get schemaTitle() {
- return this.schema.getSchema().title ?? "json-schema";
+ return this.schema.getSchema()?.title ?? "json-schema";
}
// rewrite the error message to be more human readable
private rewriteError = (error: JsonError): string => {
if (error.code === "one-of-error") {
+ console.log("raw", error?.data?.received);
return `Expected one of ${joinWithOr(
error?.data?.errors,
(data) => data.data.expected
)}`;
}
if (error.code === "type-error") {
- return `Expected \`${
+ console.log("raw", error?.data?.received);
+ return `Expected ${
error?.data?.expected && Array.isArray(error?.data?.expected)
? joinWithOr(error?.data?.expected)
: error?.data?.expected
- }\` but received \`${error?.data?.received}\``;
+ }
but received ${error?.data?.received}
`;
}
- const message = error.message.replaceAll("#/", "").replaceAll("/", ".");
-
+ const message = error.message
+ // don't mention root object
+ .replaceAll("in `#` ", "")
+ .replaceAll("/", ".")
+ .replaceAll("#.", "")
+ // replace backticks with tags
+ .replaceAll(/`([^`]*)`/gm, "$1
");
return message;
};
@@ -100,6 +108,7 @@ export class JSONValidation {
if (!errors.length) return [];
// reduce() because we want to filter out errors that don't have a pointer
return errors.reduce((acc, error) => {
+ console.log(this.rewriteError(error));
const errorPath = getErrorPath(error);
const pointer = json.pointers.get(errorPath) as JSONPointerData;
if (pointer) {
@@ -108,9 +117,12 @@ export class JSONValidation {
acc.push({
from: isPropertyError ? pointer.keyFrom : pointer.valueFrom,
to: isPropertyError ? pointer.keyTo : pointer.valueTo,
- // TODO: create a domnode and replace `` with
- // renderMessage: () => error.message,
message: this.rewriteError(error),
+ renderMessage: () => {
+ const dom = el("div", {});
+ dom.innerHTML = this.rewriteError(error);
+ return dom;
+ },
severity: "error",
source: this.schemaTitle,
});
diff --git a/src/utils/dom.ts b/src/utils/dom.ts
index d9c2975..b7def98 100644
--- a/src/utils/dom.ts
+++ b/src/utils/dom.ts
@@ -6,7 +6,7 @@
// return e;
// }
-type Attributes = "class" | "text" | "id" | "role" | "aria-label";
+type Attributes = "class" | "text" | "id" | "role" | "aria-label" | "inner";
export function el(
tagName: string,
@@ -19,6 +19,10 @@ export function el(
e.innerText = v;
return;
}
+ if (k === "inner") {
+ e.innerHTML = v;
+ return;
+ }
e.setAttribute(k, v);
});
children.forEach((c) => e.appendChild(c));
diff --git a/src/utils/formatting.ts b/src/utils/formatting.ts
index c86ab07..c492c2f 100644
--- a/src/utils/formatting.ts
+++ b/src/utils/formatting.ts
@@ -3,7 +3,7 @@
export const joinWithOr = (arr: string[], getPath?: (err: any) => any) => {
const needsComma = arr.length > 2;
let data = arr.map((err: any, i: number) => {
- const result = `\`` + (getPath ? JSON.stringify(getPath(err)) : err) + `\``;
+ const result = `` + (getPath ? getPath(err) : err) + `
`;
if (i === arr.length - 1) return "or " + result;
return result;
});
diff --git a/src/utils/schema-lib/README.md b/src/utils/schema-lib/README.md
deleted file mode 100644
index f52ae25..0000000
--- a/src/utils/schema-lib/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-## Temporary
-
-These methods are borrowed from `json-schema-library` until one of our proposals for `oneOf` is merged
diff --git a/src/utils/schema-lib/getSchema.ts b/src/utils/schema-lib/getSchema.ts
deleted file mode 100644
index 60e0c5f..0000000
--- a/src/utils/schema-lib/getSchema.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import gp from "@sagold/json-pointer";
-
-import {
- isJsonError,
- type Draft,
- type JsonSchema,
- type JsonPointer,
-} from "json-schema-library";
-
-import step from "./step";
-
-const emptyObject = {};
-
-/**
- * Returns the json-schema of a data-json-pointer.
- *
- * Notes
- * - Uses draft.step to walk through data and schema
- *
- * @param draft
- * @param pointer - json pointer in data to get the json schema for
- * @param [data] - the data object, which includes the json pointers value. This is optional, as
- * long as no oneOf, anyOf, etc statement is part of the pointers schema
- * @param [schema] - the json schema to iterate. Defaults to draft.rootSchema
- * @return json schema object of the json-pointer or an error
- */
-export default function getSchema(
- draft: Draft,
- pointer: JsonPointer,
- data?: unknown,
- schema: JsonSchema = draft.rootSchema
-): JsonSchema {
- const frags = gp.split(pointer);
- schema = draft.resolveRef(schema);
- return _get(draft, schema, frags, pointer, data);
-}
-
-function _get(
- draft: Draft,
- schema: JsonSchema,
- frags: Array,
- pointer: JsonPointer,
- data: unknown = emptyObject
-): JsonSchema {
- if (frags.length === 0) {
- return draft.resolveRef(schema);
- }
-
- const key = frags.shift(); // step key
- // @ts-expect-error
- schema = step(draft, key, schema, data, pointer); // step schema
- if (isJsonError(schema)) {
- return schema;
- }
- // @ts-expect-error
- data = data[key]; // step data
- return _get(draft, schema, frags, `${pointer}/${key}`, data);
-}
diff --git a/src/utils/schema-lib/step.ts b/src/utils/schema-lib/step.ts
deleted file mode 100644
index bbf5493..0000000
--- a/src/utils/schema-lib/step.ts
+++ /dev/null
@@ -1,218 +0,0 @@
-import {
- getTypeOf,
- Draft,
- JsonSchema,
- JsonPointer,
- JsonError,
- isJsonError,
- reduceSchema,
-} from "json-schema-library";
-
-// @ts-expect-error
-import errors from "json-schema-library/dist/module/lib/validation/errors";
-
-// @ts-expect-error
-import createSchemaOf from "json-schema-library/dist/module/lib/createSchemaOf";
-
-// import getTypeOf from "./getTypeOf";
-// import errors from "./validation/errors";
-// import { JsonSchema, JsonPointer, JsonError, isJsonError } from "./types";
-// import { Draft } from "./draft";
-
-type StepFunction = (
- draft: Draft,
- key: string,
- schema: JsonSchema,
- data: any,
- pointer: JsonPointer
-) => JsonSchema | JsonError;
-
-const stepType: Record = {
- array: (draft, key, schema, data, pointer) => {
- const itemValue = data?.[key];
- const itemsType = getTypeOf(schema.items);
-
- if (itemsType === "object") {
- // @spec: ignore additionalItems, when items is schema-object
- return (
- reduceSchema(draft, schema.items, itemValue) ||
- draft.resolveRef(schema.items)
- );
- }
-
- if (itemsType === "array") {
- // @draft >= 7 bool schema, items:[true, false]
- if (schema.items[key] === true) {
- return createSchemaOf(itemValue);
- }
- // @draft >= 7 bool schema, items:[true, false]
- if (schema.items[key] === false) {
- return errors.invalidDataError({
- key,
- value: itemValue,
- pointer,
- });
- }
-
- if (schema.items[key]) {
- return draft.resolveRef(schema.items[key]);
- }
-
- if (schema.additionalItems === false) {
- return errors.additionalItemsError({
- key,
- value: itemValue,
- pointer,
- });
- }
-
- if (
- schema.additionalItems === true ||
- schema.additionalItems === undefined
- ) {
- return createSchemaOf(itemValue);
- }
-
- if (getTypeOf(schema.additionalItems) === "object") {
- return schema.additionalItems;
- }
-
- throw new Error(
- `Invalid schema ${JSON.stringify(schema, null, 4)} for ${JSON.stringify(
- data,
- null,
- 4
- )}`
- );
- }
-
- if (schema.additionalItems !== false && itemValue) {
- // @todo reevaluate: incomplete schema is created here
- // @todo support additionalItems: {schema}
- return createSchemaOf(itemValue);
- }
-
- return new Error(
- `Invalid array schema for ${key} at ${pointer}`
- ) as JsonError;
- },
-
- object: (draft, key, schema, data, pointer) => {
- schema = reduceSchema(draft, schema, data);
-
- // @feature properties
- const property = schema?.properties?.[key];
- if (property !== undefined) {
- // @todo patternProperties also validate properties
-
- // @feature boolean schema
- if (property === false) {
- return errors.forbiddenPropertyError({
- property: key,
- value: data,
- pointer: `${pointer}`,
- });
- } else if (property === true) {
- return createSchemaOf(data?.[key]);
- }
-
- const targetSchema = draft.resolveRef(property);
- if (isJsonError(targetSchema)) {
- return targetSchema;
- }
-
- // check if there is a oneOf selection, which must be resolved
- if (targetSchema && Array.isArray(targetSchema.oneOf)) {
- if (data === undefined || data[key] === undefined) {
- return targetSchema;
- }
- // @special case: this is a mix of a schema and optional definitions
- // we resolve the schema here and add the original schema to `oneOfSchema`
- return draft.resolveOneOf(data[key], targetSchema, `${pointer}/${key}`);
- }
-
- // resolved schema or error
- if (targetSchema) {
- return targetSchema;
- }
- }
-
- // @feature patternProperties
- const { patternProperties } = schema;
- if (getTypeOf(patternProperties) === "object") {
- // find matching property key
- let regex;
- const patterns = Object.keys(patternProperties);
- for (let i = 0, l = patterns.length; i < l; i += 1) {
- regex = new RegExp(patterns[i]);
- if (regex.test(key)) {
- return patternProperties[patterns[i]];
- }
- }
- }
-
- // @feature additionalProperties
- const { additionalProperties } = schema;
- if (getTypeOf(additionalProperties) === "object") {
- return schema.additionalProperties;
- }
- if (
- data &&
- (additionalProperties === undefined || additionalProperties === true)
- ) {
- return createSchemaOf(data[key]);
- }
-
- return errors.unknownPropertyError({
- property: key,
- value: data,
- pointer: `${pointer}`,
- });
- },
-};
-
-/**
- * Returns the json-schema of the given object property or array item.
- * e.g. it steps by one key into the data
- *
- * This helper determines the location of the property within the schema (additional properties, oneOf, ...) and
- * returns the correct schema.
- *
- * @param draft - validator
- * @param key - property-name or array-index
- * @param schema - json schema of current data
- * @param data - parent of key
- * @param [pointer] - pointer to schema and data (parent of key)
- * @return Schema or Error if failed resolving key
- */
-export default function step(
- draft: Draft,
- key: string | number,
- schema: JsonSchema,
- data?: any,
- pointer: JsonPointer = "#"
-): JsonSchema | JsonError {
- // @draft >= 4 ?
- if (Array.isArray(schema.type)) {
- const dataType = getTypeOf(data);
- if (schema.type.includes(dataType)) {
- return stepType[dataType](draft, `${key}`, schema, data, pointer);
- }
- return draft.errors.typeError({
- value: data,
- pointer,
- expected: schema.type,
- received: dataType,
- });
- }
-
- const expectedType = schema.type || getTypeOf(data);
- const stepFunction = stepType[expectedType];
- if (stepFunction) {
- return stepFunction(draft, `${key}`, schema, data, pointer);
- }
-
- return new Error(
- `Unsupported schema type ${schema.type} for key ${key}`
- ) as JsonError;
-}