Skip to content

Commit af54dde

Browse files
committed
change: replace method validateAsync with validate.errorsAsync
1 parent a38a1a6 commit af54dde

13 files changed

+159
-259
lines changed

README.md

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[![Npm package version](https://badgen.net/npm/v/json-schema-library)](https://github.com/sagold/json-schema-library/actions/workflows/ci.yaml) [![CI](https://github.com/sagold/json-schema-library/actions/workflows/ci.yaml/badge.svg)](https://github.com/sagold/json-schema-library/actions/workflows/ci.yaml) ![Types](https://badgen.net/npm/types/json-schema-library)
1+
injectWorkspacePackages: true[![Npm package version](https://badgen.net/npm/v/json-schema-library)](https://github.com/sagold/json-schema-library/actions/workflows/ci.yaml) [![CI](https://github.com/sagold/json-schema-library/actions/workflows/ci.yaml/badge.svg)](https://github.com/sagold/json-schema-library/actions/workflows/ci.yaml) ![Types](https://badgen.net/npm/types/json-schema-library)
22

33
<h1 align="center">
44
<img src="./docs/json-schema-library-10.png" width="192" alt="json-schema-library">
@@ -180,8 +180,7 @@ Please note that these benchmarks refer to validation only. _json-schema-library
180180
[**reduceNode**](#reducenode) ·
181181
[**toDataNodes**](#todatanodes) ·
182182
[**toSchemaNodes**](#toschemanodes) ·
183-
[**validate**](#validate) ·
184-
[**validateAsync**](#validateasync)
183+
[**validate**](#validate)
185184

186185
</details>
187186

@@ -862,18 +861,70 @@ expect(errors).to.deep.equal([
862861

863862
</details>
864863

865-
### validateAsync
864+
You can also use async validators to validate data with json-schema. For this, another property asyncErrors is exposed on validate:
866865

867-
Per default all _json-schema-library_ validators are sync, but adding custom async validators is supported. To resolve async validators use `validateAsync`:
866+
```ts
867+
const { isValid, errors, errorsAsync } = compileSchema(myJsonSchema).validate(myData);
868+
869+
if (errorsAsync.length > 0) {
870+
const additionalErrors = (await Promise.all(errorsAsync)).filter((err) => err != null);
871+
}
872+
```
873+
874+
Per default _json-schema-library_ does not have async validators, so `errorsAsync` is always empty. If you add async validators, a list of `Promise<JsonError|undefined>` is return and you need to resolve and filter non-errors (undefined) yourself.
868875

869-
~~Optional support for `onError` helper, which is invoked for each error (after being resolved)~~
876+
> **Note** `isValid` only refers to errors. `errorsAsync` has to be evaluated separately
877+
878+
<details><summary>Example Async Validation</summary>
879+
880+
```ts
881+
import { JsonSchemaValidator, draft2020 } from "json-schema-library";
882+
// return Promise<JsonError>
883+
const customValidator: JsonSchemaValidator = async ({ node, pointer, data }) => {
884+
return node.createError("type-error", {
885+
schema: {},
886+
pointer,
887+
value: data
888+
});
889+
};
890+
891+
const draftList = [
892+
extendDraft(jsonEditorDraft, {
893+
keywords: {
894+
custom: customValidator
895+
}
896+
})
897+
];
898+
899+
const { isValid, errorsAsync } = compileSchema({ custom: true }).validate("data");
900+
console.log(isValid, errors.length); // true, 0
901+
902+
const errors = await Promise.all(errorsAsync);
903+
console.log(errors); /// [{ code: "type-error", value: "data", pointer: "#", ... }]
904+
```
905+
906+
</details>
870907

871908
```ts
872-
compileSchema(mySchema)
873-
.validateAsync(myData)
874-
.then(({ valid, error }) => console.log(errors));
909+
const myJsonSchema: JsonSchema = {
910+
type: "object",
911+
additionalProperties: false
912+
};
913+
914+
const { errors } = compileSchema(myJsonSchema).validate({ name: "my-data" });
915+
916+
expect(errors).to.deep.equal([
917+
{
918+
type: "error",
919+
code: "no-additional-properties-error",
920+
message: "Additional property `name` in `#` is not allowed",
921+
data: { property: "name", properties: [], pointer: "#" }
922+
}
923+
]);
875924
```
876925

926+
</details>
927+
877928
## Draft Customization
878929

879930
[**Extending a Draft**](#extending-a-draft) · [**Keyword**](#keyword)

src/Draft.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import type { Keyword } from "./Keyword";
1+
import type { JsonSchemaValidator, Keyword } from "./Keyword";
22
import { copyDraft } from "./utils/copyDraft";
33
import { createSchema } from "./methods/createSchema";
44
import { toDataNodes } from "./methods/toDataNodes";
55
import { ErrorConfig } from "./types";
6-
import { formats } from "./formats/formats";
76
import { getChildSelection } from "./methods/getChildSelection";
87
import { getData } from "./methods/getData";
98

@@ -27,7 +26,7 @@ export interface Draft {
2726
$schema?: string;
2827
/** draft errors (this can still be global) */
2928
errors: ErrorConfig;
30-
formats: typeof formats;
29+
formats: Record<string, JsonSchemaValidator>;
3130
}
3231

3332
type PartialDraft = Partial<Omit<Draft, "errors" | "formats">> & {

src/Keyword.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export type JsonSchemaValidatorParams = { pointer?: string; data: unknown; node:
3636
export interface JsonSchemaValidator {
3737
toJSON?: () => string;
3838
order?: number;
39-
(options: JsonSchemaValidatorParams): undefined | JsonError | ValidationResult[];
39+
(options: JsonSchemaValidatorParams): undefined | ValidationResult | ValidationResult[];
4040
}
4141

4242
export type Keyword = {

src/SchemaNode.ts

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
import copy from "fast-copy";
22
import sanitizeErrors from "./utils/sanitizeErrors";
33
import settings from "./settings";
4-
import type { JsonSchemaReducer, JsonSchemaResolver, JsonSchemaValidator, Keyword, ValidationPath } from "./Keyword";
4+
import type {
5+
JsonSchemaReducer,
6+
JsonSchemaResolver,
7+
JsonSchemaValidator,
8+
Keyword,
9+
ValidationPath,
10+
ValidationResult
11+
} from "./Keyword";
512
import { createSchema } from "./methods/createSchema";
613
import { Draft } from "./Draft";
714
import { toSchemaNodes } from "./methods/toSchemaNodes";
@@ -299,30 +306,27 @@ export const SchemaNodeMethods = {
299306
/**
300307
* @returns validation result of data validated by this node's JSON Schema
301308
*/
302-
validate(data: unknown, pointer = "#", path: ValidationPath = []): { valid: boolean; errors: JsonError[] } {
309+
validate(data: unknown, pointer = "#", path: ValidationPath = []) {
303310
const errors = validateNode(this, data, pointer, path) ?? [];
311+
const syncErrors: JsonError[] = [];
304312
const flatErrorList = sanitizeErrors(Array.isArray(errors) ? errors : [errors]).filter(isJsonError);
305-
return {
313+
314+
const errorsAsync: Promise<JsonError | undefined>[] = [];
315+
sanitizeErrors(Array.isArray(errors) ? errors : [errors]).forEach((error) => {
316+
if (isJsonError(error)) {
317+
syncErrors.push(error);
318+
} else if (error instanceof Promise) {
319+
errorsAsync.push(error);
320+
}
321+
});
322+
323+
const result: { valid: boolean; errors: JsonError[]; errorsAsync: Promise<JsonError | undefined>[] } = {
306324
valid: flatErrorList.length === 0,
307-
errors: flatErrorList
325+
errors: syncErrors,
326+
errorsAsync
308327
};
309-
},
310328

311-
/**
312-
* @returns a promise which resolves to validation-result
313-
*/
314-
async validateAsync(
315-
data: unknown,
316-
pointer = "#",
317-
path: ValidationPath = []
318-
): Promise<{ valid: boolean; errors: JsonError[] }> {
319-
const errors = validateNode(this, data, pointer, path) ?? [];
320-
let resolvedErrors = await Promise.all(sanitizeErrors(Array.isArray(errors) ? errors : [errors]));
321-
resolvedErrors = sanitizeErrors(resolvedErrors) as JsonError[];
322-
return {
323-
valid: resolvedErrors.length === 0,
324-
errors: resolvedErrors
325-
};
329+
return result;
326330
},
327331

328332
/**

src/compileSchema.reduceSchema.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,6 @@ describe("compileSchema : reduceNode", () => {
128128
dependencies: { one: ["two"], two: { type: "number" } }
129129
});
130130
const { node: reduced } = node.reduceNode({});
131-
console.log("reudced schema", reduced.schema);
132131
assert.deepEqual(reduced.schema, {
133132
type: "object",
134133
properties: { one: { type: "string" } }

src/compileSchema.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ import { TemplateOptions } from "./methods/getData";
1111
import { SchemaNode, SchemaNodeMethods, addKeywords, isSchemaNode } from "./SchemaNode";
1212

1313
export type CompileOptions = {
14-
drafts: Draft[];
15-
remote: SchemaNode;
16-
formatAssertion: boolean | "meta-schema";
14+
drafts?: Draft[];
15+
remote?: SchemaNode;
16+
formatAssertion?: boolean | "meta-schema";
1717
getDataDefaultOptions?: TemplateOptions;
1818
};
1919

@@ -28,7 +28,7 @@ function getDraft(drafts: Draft[], $schema: string) {
2828
* wrapping each schema with utilities and as much preevaluation is possible. Each
2929
* node will be reused for each task, but will create a compiledNode for bound data.
3030
*/
31-
export function compileSchema(schema: JsonSchema, options: Partial<CompileOptions> = {}) {
31+
export function compileSchema(schema: JsonSchema, options: CompileOptions = {}) {
3232
/** @todo this option has to be passed to all drafts (remotes) */
3333
let formatAssertion = options.formatAssertion ?? true;
3434
const drafts = options.drafts ?? defaultDrafts;

src/compileSchema.validate.test.ts

Lines changed: 22 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { compileSchema } from "./compileSchema";
22
import { strict as assert } from "assert";
33
import { Draft, JsonError, SchemaNode } from "./types";
44
import { draft2020 } from "./draft2020";
5+
import { ValidationResult } from "./Keyword";
56

67
describe("compileSchema.validate", () => {
78
describe("integer", () => {
@@ -948,29 +949,13 @@ describe("compileSchema.validate : format", () => {
948949
});
949950
});
950951

951-
describe("compileSchema.validateAsync", () => {
952-
it("should return a promise", () => {
953-
const promise = compileSchema({
954-
type: "number"
955-
}).validateAsync(4);
956-
assert(promise instanceof Promise);
957-
});
958-
952+
describe("compileSchema.validate - errorsAsync", () => {
959953
it("should resolve successfull with an empty error", async () => {
960-
const promise = compileSchema({
954+
const { errorsAsync } = compileSchema({
961955
type: "number"
962-
}).validateAsync(4);
963-
const { errors } = await promise;
964-
assert.deepEqual(errors.length, 0);
965-
});
966-
967-
it("should resolve with errors for a failed validation", async () => {
968-
const promise = compileSchema({
969-
type: "number"
970-
}).validateAsync("4");
971-
const { errors } = await promise;
972-
assert.deepEqual(errors.length, 1);
973-
assert.deepEqual(errors[0].code, "type-error");
956+
}).validate(4);
957+
const asyncErrors = await Promise.all(errorsAsync);
958+
assert.deepEqual(asyncErrors.length, 0);
974959
});
975960

976961
describe("async validation", () => {
@@ -979,92 +964,44 @@ describe("compileSchema.validateAsync", () => {
979964
draft = {
980965
...draft2020,
981966
keywords: [
982-
// @ts-expect-error asd
983967
...draft2020.keywords,
984968
{
985969
id: "async",
986970
keyword: "async-error",
987971
addValidate: (node) => node.schema.asyncError != null,
988-
// @ts-expect-error asd
989-
validate: ({ node }) => {
972+
validate: async ({ node }): Promise<JsonError> => {
990973
if (node.schema.asyncError === false) {
991-
return;
974+
return undefined;
992975
}
993-
return new Promise((resolve) =>
994-
resolve([
995-
node.createError("type-error", {
996-
schema: {},
997-
pointer: "",
998-
value: ""
999-
})
1000-
])
1001-
) as Promise<JsonError[]>;
976+
return node.createError("type-error", {
977+
schema: {},
978+
pointer: "",
979+
value: ""
980+
});
1002981
}
1003982
}
1004983
]
1005984
};
1006985

1007986
it("should resolve async validation returning no error", async () => {
1008-
const { errors } = await compileSchema(
987+
const { errors, errorsAsync } = compileSchema(
1009988
{ type: "number", asyncError: false },
1010989
{ drafts: [draft] }
1011-
).validateAsync(4);
990+
).validate(4);
991+
const asyncErrors = await Promise.all(errorsAsync);
1012992
assert.deepEqual(errors.length, 0);
993+
assert.deepEqual(asyncErrors.length, 0);
1013994
});
1014995

1015996
it("should resolve async validation errors", async () => {
1016-
const { errors } = await compileSchema(
997+
const { errorsAsync } = compileSchema(
1017998
{ type: "number", asyncError: true },
1018999
{ drafts: [draft] }
1019-
).validateAsync(4);
1020-
assert.deepEqual(errors.length, 1);
1021-
assert.deepEqual(errors[0].code, "type-error");
1000+
).validate(4);
1001+
const asyncErrors = await Promise.all(errorsAsync);
1002+
assert.deepEqual(asyncErrors.length, 1);
1003+
assert.deepEqual(asyncErrors[0].code, "type-error");
10221004
});
10231005
});
10241006
});
1025-
1026-
// describe("on-error", () => {
1027-
// before(() => {
1028-
// // adds an async validation helper to { type: 'string', asyncError: true }
1029-
// // @ts-expect-error type mismatch of vladation function
1030-
// addValidator.keyword(draft, "string", "async-error", (node) => {
1031-
// return node.schema.asyncError
1032-
// ? new Promise((resolve) =>
1033-
// // eslint-disable-next-line max-nested-callbacks
1034-
// resolve({
1035-
// type: "error",
1036-
// name: "async-error",
1037-
// code: "test-async-error",
1038-
// message: "custom test error"
1039-
// })
1040-
// )
1041-
// : Promise.resolve();
1042-
// });
1043-
// });
1044-
1045-
// it("should call onProgress immediately with error", async () => {
1046-
// const errors: JsonError[] = [];
1047-
// return validateAsync(
1048-
// draft,
1049-
// {
1050-
// async: "test async progres",
1051-
// anotherError: 44
1052-
// },
1053-
// {
1054-
// schema: {
1055-
// type: "object",
1056-
// properties: {
1057-
// async: { type: "string", asyncError: true },
1058-
// anotherError: { type: "string" }
1059-
// }
1060-
// },
1061-
// onError: (err) => errors.push(err)
1062-
// }
1063-
// ).then(() => {
1064-
// assert.deepEqual(errors.length, 2);
1065-
// assert.deepEqual(errors[0].name).to.eq("type-error");
1066-
// assert.deepEqual(errors[1].name).to.eq("async-error");
1067-
// });
1068-
// });
1069-
// });
10701007
});

0 commit comments

Comments
 (0)