@tsed/ajv 8.0.1 → 8.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -2,13 +2,14 @@
2
2
  "name": "@tsed/ajv",
3
3
  "description": "AJV package for Ts.ED framework",
4
4
  "type": "module",
5
- "version": "8.0.1",
5
+ "version": "8.0.3",
6
6
  "source": "./src/index.ts",
7
7
  "main": "./lib/esm/index.js",
8
8
  "module": "./lib/esm/index.js",
9
9
  "typings": "./lib/types/index.d.ts",
10
10
  "exports": {
11
11
  ".": {
12
+ "@tsed/source": "./src/index.ts",
12
13
  "types": "./lib/types/index.d.ts",
13
14
  "import": "./lib/esm/index.js",
14
15
  "default": "./lib/esm/index.js"
@@ -27,22 +28,22 @@
27
28
  "tslib": "2.7.0"
28
29
  },
29
30
  "devDependencies": {
30
- "@tsed/barrels": "8.0.1",
31
- "@tsed/core": "8.0.1",
32
- "@tsed/di": "8.0.1",
33
- "@tsed/exceptions": "8.0.1",
34
- "@tsed/schema": "8.0.1",
35
- "@tsed/typescript": "8.0.1",
31
+ "@tsed/barrels": "8.0.3",
32
+ "@tsed/core": "8.0.3",
33
+ "@tsed/di": "8.0.3",
34
+ "@tsed/exceptions": "8.0.3",
35
+ "@tsed/schema": "8.0.3",
36
+ "@tsed/typescript": "8.0.3",
36
37
  "ajv": "^8.17.1",
37
38
  "eslint": "9.12.0",
38
39
  "typescript": "5.4.5",
39
40
  "vitest": "2.1.2"
40
41
  },
41
42
  "peerDependencies": {
42
- "@tsed/core": "8.0.1",
43
- "@tsed/di": "8.0.1",
44
- "@tsed/exceptions": "8.0.1",
45
- "@tsed/schema": "8.0.1",
43
+ "@tsed/core": "8.0.3",
44
+ "@tsed/di": "8.0.3",
45
+ "@tsed/exceptions": "8.0.3",
46
+ "@tsed/schema": "8.0.3",
46
47
  "ajv": ">=8.9.0",
47
48
  "ajv-errors": ">=3.0.0"
48
49
  },
package/readme.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p style="text-align: center" align="center">
2
- <a href="https://tsed.io" target="_blank"><img src="https://tsed.io/tsed-og.png" width="200" alt="Ts.ED logo"/></a>
2
+ <a href="https://tsed.dev" target="_blank"><img src="https://tsed.dev/tsed-og.png" width="200" alt="Ts.ED logo"/></a>
3
3
  </p>
4
4
 
5
5
  <div align="center">
@@ -15,7 +15,7 @@
15
15
 
16
16
  </div>
17
17
 
18
- A package of Ts.ED framework. See website: https://tsed.io/#/tutorials/ajv
18
+ A package of Ts.ED framework. See website: https://tsed.dev/tutorials/ajv.html
19
19
 
20
20
  ## Installation
21
21
 
@@ -38,11 +38,11 @@ export class Server {}
38
38
 
39
39
  ### Decorators
40
40
 
41
- See our API documentation on https://tsed.io/#/api/index
41
+ See our API documentation on https://tsed.dev/api/index
42
42
 
43
43
  ## Example
44
44
 
45
- A model can used on a method controller along with [@BodyParams](http://tsed.io/api/common/filters/decorators/BodyParams.html) or other decorators, and will
45
+ A model can used on a method controller along with [@BodyParams](https://tsed.dev/api/common/filters/decorators/BodyParams.html) or other decorators, and will
46
46
  be validated by Ajv.
47
47
 
48
48
  ```typescript
@@ -98,7 +98,7 @@ export class Server {}
98
98
 
99
99
  ## Contributors
100
100
 
101
- Please read [contributing guidelines here](https://tsed.io/contributing.html)
101
+ Please read [contributing guidelines here](https://tsed.dev/contributing.html)
102
102
 
103
103
  <a href="https://github.com/tsedio/tsed/graphs/contributors"><img src="https://opencollective.com/tsed/contributors.svg?width=890" /></a>
104
104
 
@@ -0,0 +1,24 @@
1
+ import {StoreSet, useDecorators} from "@tsed/core";
2
+ import {Injectable} from "@tsed/di";
3
+ import {AsyncFormatDefinition, FormatDefinition} from "ajv";
4
+
5
+ export type FormatsOptions = Omit<FormatDefinition<any>, "validate" | "compare"> | Omit<AsyncFormatDefinition<any>, "validate" | "compare">;
6
+
7
+ /**
8
+ * Create a new custom formats validator
9
+ * @param name
10
+ * @param options
11
+ * @decorator
12
+ * @ajv
13
+ */
14
+ export function Formats(name: string, options: FormatsOptions = {}): ClassDecorator {
15
+ return useDecorators(
16
+ Injectable({
17
+ type: "ajv:formats"
18
+ }),
19
+ StoreSet("ajv:formats", {
20
+ name,
21
+ options
22
+ })
23
+ );
24
+ }
@@ -0,0 +1,26 @@
1
+ import {StoreSet, useDecorators} from "@tsed/core";
2
+ import {Injectable} from "@tsed/di";
3
+ import {JsonSchema} from "@tsed/schema";
4
+ import {AnySchemaObject, KeywordDefinition} from "ajv";
5
+
6
+ export type KeywordOptions = Partial<Omit<KeywordDefinition, "metaSchema">> & {
7
+ metaSchema?: AnySchemaObject | JsonSchema;
8
+ };
9
+
10
+ /**
11
+ * Create new keyword custom validator
12
+ * @param options
13
+ * @decorator
14
+ * @ajv
15
+ */
16
+ export function Keyword(options: KeywordOptions): ClassDecorator {
17
+ return useDecorators(
18
+ Injectable({
19
+ type: "ajv:keyword"
20
+ }),
21
+ StoreSet("ajv:keyword", {
22
+ ...options,
23
+ metaSchema: options.metaSchema && options.metaSchema.toJSON ? options.metaSchema.toJSON() : options.metaSchema
24
+ })
25
+ );
26
+ }
@@ -0,0 +1,11 @@
1
+ import {BadRequest} from "@tsed/exceptions";
2
+
3
+ export class AjvValidationError extends BadRequest {
4
+ public name: string = "AJV_VALIDATION_ERROR";
5
+ public errors: any[];
6
+
7
+ constructor(message: string, errors: any[] = []) {
8
+ super(message);
9
+ this.errors = errors;
10
+ }
11
+ }
package/src/index.ts ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @file Automatically generated by @tsed/barrels.
3
+ */
4
+ export * from "./decorators/formats.js";
5
+ export * from "./decorators/keyword.js";
6
+ export * from "./errors/AjvValidationError.js";
7
+ export * from "./interfaces/FormatsMethods.js";
8
+ export * from "./interfaces/IAjvSettings.js";
9
+ export * from "./interfaces/interfaces.js";
10
+ export * from "./interfaces/KeywordMethods.js";
11
+ export * from "./services/Ajv.js";
12
+ export * from "./services/AjvService.js";
13
+ export * from "./utils/defaultErrorFormatter.js";
14
+ export * from "./utils/getPath.js";
@@ -0,0 +1,5 @@
1
+ export interface FormatsMethods<T extends string | number> {
2
+ compare?: (data1: T, data2: T) => number | undefined;
3
+
4
+ validate(data: T): boolean;
5
+ }
@@ -0,0 +1,23 @@
1
+ import {ErrorObject, Options} from "ajv";
2
+
3
+ /**
4
+ *
5
+ */
6
+ export interface AjvErrorObject extends ErrorObject {
7
+ modelName: string;
8
+ collectionName?: string;
9
+ }
10
+
11
+ /**
12
+ *
13
+ */
14
+ export type ErrorFormatter = (error: AjvErrorObject) => string;
15
+
16
+ /**
17
+ *
18
+ */
19
+ export interface IAjvSettings extends Options {
20
+ Ajv?: any;
21
+ errorFormatter?: ErrorFormatter;
22
+ returnsCoercedValues?: boolean;
23
+ }
@@ -0,0 +1,23 @@
1
+ import {AnySchemaObject, KeywordCxt, SchemaObjCxt, ValidateFunction} from "ajv";
2
+
3
+ export interface AjvDataValidationCtx<T extends string | number = string | number> {
4
+ dataPath: string;
5
+ parentData: {[K in T]: any}; // object or array
6
+ parentDataProperty: T; // string or number
7
+ rootData: Record<string, any> | any[];
8
+ dynamicAnchors: {[Ref in string]?: ValidateFunction};
9
+ }
10
+
11
+ export interface AjvDataValidateFunction {
12
+ (...args: Parameters<ValidateFunction>): boolean | Promise<any>;
13
+ }
14
+
15
+ export interface AjvSchemaValidateFunction {
16
+ (schema: any, data: any, parentSchema?: AnySchemaObject, dataCxt?: AjvDataValidationCtx): boolean | Promise<any>;
17
+ }
18
+
19
+ export interface KeywordMethods {
20
+ code?: (cxt: KeywordCxt, ruleType?: string) => void;
21
+ compile?: (schema: any, parentSchema: AnySchemaObject, it: SchemaObjCxt) => AjvDataValidateFunction;
22
+ validate?: AjvSchemaValidateFunction | AjvDataValidateFunction;
23
+ }
@@ -0,0 +1,10 @@
1
+ import {IAjvSettings} from "./IAjvSettings.js";
2
+
3
+ declare global {
4
+ namespace TsED {
5
+ interface Configuration {
6
+ // @ts-ignore
7
+ ajv?: IAjvSettings;
8
+ }
9
+ }
10
+ }
@@ -0,0 +1,39 @@
1
+ import {PlatformTest} from "@tsed/platform-http/testing";
2
+ import {Ajv} from "ajv";
3
+
4
+ describe("Ajv", () => {
5
+ beforeEach(() => PlatformTest.create());
6
+ afterEach(() => PlatformTest.reset());
7
+ it("should create a new Ajv instance", async () => {
8
+ const ajv = await PlatformTest.invoke<Ajv>(Ajv);
9
+
10
+ expect(ajv).toBeInstanceOf(Ajv);
11
+ expect(
12
+ ajv.validate(
13
+ {
14
+ type: "object"
15
+ },
16
+ {}
17
+ )
18
+ ).toEqual(true);
19
+
20
+ ajv.validate(
21
+ {
22
+ type: "object"
23
+ },
24
+ []
25
+ );
26
+
27
+ expect(ajv.errors).toEqual([
28
+ {
29
+ instancePath: "",
30
+ keyword: "type",
31
+ message: "must be object",
32
+ params: {
33
+ type: "object"
34
+ },
35
+ schemaPath: "#/type"
36
+ }
37
+ ]);
38
+ });
39
+ });
@@ -0,0 +1,84 @@
1
+ import {cleanObject} from "@tsed/core";
2
+ import {constant, inject, injectable, injector, InjectorService, ProviderScope} from "@tsed/di";
3
+ import {Ajv, Format, KeywordDefinition, Options, Vocabulary} from "ajv";
4
+ import AjvErrors from "ajv-errors";
5
+ import AjvFormats from "ajv-formats";
6
+
7
+ import {FormatsMethods} from "../interfaces/FormatsMethods.js";
8
+ import {IAjvSettings} from "../interfaces/IAjvSettings.js";
9
+
10
+ function getHandler(key: string, service: any) {
11
+ if (service[key]) {
12
+ return service[key].bind(service);
13
+ }
14
+ }
15
+
16
+ function getKeywordProviders() {
17
+ return injector().getProviders("ajv:keyword");
18
+ }
19
+
20
+ function bindKeywords(): Vocabulary {
21
+ return getKeywordProviders().map((provider) => {
22
+ const options = provider.store.get<Omit<KeywordDefinition, "compile">>("ajv:keyword", {})!;
23
+ const service = inject(provider.token);
24
+
25
+ return <KeywordDefinition>cleanObject({
26
+ coerceTypes: "array",
27
+ ...options,
28
+ validate: getHandler("validate", service),
29
+ compile: getHandler("compile", service),
30
+ code: getHandler("code", service),
31
+ macro: getHandler("macro", service)
32
+ });
33
+ });
34
+ }
35
+
36
+ function getFormatsProviders() {
37
+ return injector().getProviders("ajv:formats");
38
+ }
39
+
40
+ function getFormats(): {name: string; options: Format}[] {
41
+ return getFormatsProviders().map((provider) => {
42
+ const {name, options} = provider.store.get<any>("ajv:formats", {})!;
43
+ const service = inject<FormatsMethods<any>>(provider.token);
44
+
45
+ return {
46
+ name,
47
+ options: {
48
+ ...options,
49
+ validate: service.validate.bind(service),
50
+ compare: service.compare?.bind(service)
51
+ }
52
+ };
53
+ });
54
+ }
55
+
56
+ injectable(Ajv)
57
+ .scope(ProviderScope.SINGLETON)
58
+ .factory(() => {
59
+ const {errorFormatter, keywords = [], ...props} = constant<IAjvSettings>("ajv") || {};
60
+ const options: Options = {
61
+ verbose: false,
62
+ coerceTypes: true,
63
+ strict: false,
64
+ keywords: [...keywords, ...bindKeywords()],
65
+ discriminator: true,
66
+ allErrors: true,
67
+ ...props
68
+ };
69
+
70
+ // @ts-ignore
71
+ const ajv = new Ajv(options);
72
+
73
+ // add support for custom error messages
74
+ // @ts-ignore
75
+ AjvErrors(ajv);
76
+ // @ts-ignore
77
+ AjvFormats(ajv as any);
78
+
79
+ getFormats().forEach(({name, options}) => {
80
+ ajv.addFormat(name, options);
81
+ });
82
+
83
+ return ajv;
84
+ });
@@ -0,0 +1,47 @@
1
+ import {catchAsyncError} from "@tsed/core";
2
+ import {PlatformTest} from "@tsed/platform-http/testing";
3
+ import {object, string} from "@tsed/schema";
4
+
5
+ import {AjvService} from "./AjvService.js";
6
+
7
+ describe("AjvService", () => {
8
+ beforeEach(() => PlatformTest.create());
9
+ afterEach(() => PlatformTest.reset());
10
+
11
+ it("should use the function api as schema", async () => {
12
+ const ajvService = PlatformTest.get<AjvService>(AjvService);
13
+
14
+ const error: any = await catchAsyncError(() => ajvService.validate("test", string().minLength(5)));
15
+ expect(error.errors).toEqual([
16
+ {
17
+ data: "test",
18
+ dataPath: "",
19
+ instancePath: "",
20
+ keyword: "minLength",
21
+ message: "must NOT have fewer than 5 characters",
22
+ params: {
23
+ limit: 5
24
+ },
25
+ schemaPath: "#/minLength"
26
+ }
27
+ ]);
28
+ });
29
+
30
+ it("should use the function api as schema (required)", async () => {
31
+ const ajvService = PlatformTest.get<AjvService>(AjvService);
32
+
33
+ const error: any = await catchAsyncError(() => ajvService.validate({}, object({test: string().required()})));
34
+ expect(error.errors).toEqual([
35
+ {
36
+ dataPath: ".test",
37
+ instancePath: "",
38
+ keyword: "required",
39
+ message: "must have required property 'test'",
40
+ params: {
41
+ missingProperty: "test"
42
+ },
43
+ schemaPath: "#/required"
44
+ }
45
+ ]);
46
+ });
47
+ });
@@ -0,0 +1,116 @@
1
+ import "./Ajv.js";
2
+
3
+ import {deepClone, getValue, nameOf, prototypeOf, setValue, Type} from "@tsed/core";
4
+ import {constant, inject, injectable} from "@tsed/di";
5
+ import {getJsonSchema, JsonEntityStore, JsonSchema, JsonSchemaObject} from "@tsed/schema";
6
+ import {Ajv, ErrorObject} from "ajv";
7
+
8
+ import {AjvValidationError} from "../errors/AjvValidationError.js";
9
+ import {AjvErrorObject, ErrorFormatter} from "../interfaces/IAjvSettings.js";
10
+ import {defaultErrorFormatter} from "../utils/defaultErrorFormatter.js";
11
+ import {getPath} from "../utils/getPath.js";
12
+
13
+ export interface AjvValidateOptions extends Record<string, any> {
14
+ schema?: JsonSchema | Partial<JsonSchemaObject>;
15
+ type?: Type<any> | any;
16
+ collectionType?: Type<any> | any;
17
+ }
18
+
19
+ export class AjvService {
20
+ readonly name = "ajv";
21
+ protected errorFormatter = constant<ErrorFormatter>("ajv.errorFormatter", defaultErrorFormatter);
22
+ protected returnsCoercedValues = constant<boolean>("ajv.returnsCoercedValues");
23
+ protected ajv = inject(Ajv);
24
+
25
+ async validate(value: any, options: AjvValidateOptions | JsonSchema): Promise<any> {
26
+ let {schema: defaultSchema, type, collectionType, ...additionalOptions} = this.mapOptions(options);
27
+
28
+ const schema = defaultSchema || getJsonSchema(type, {...additionalOptions, customKeys: true});
29
+
30
+ if (schema) {
31
+ const localValue = this.returnsCoercedValues ? value : deepClone(value);
32
+ const validate = this.ajv.compile(schema);
33
+
34
+ const valid = await validate(localValue);
35
+ const {errors} = validate;
36
+
37
+ if (!valid && errors) {
38
+ throw this.mapErrors(errors, {
39
+ type,
40
+ collectionType,
41
+ async: true,
42
+ value: localValue
43
+ });
44
+ }
45
+
46
+ if (this.returnsCoercedValues) {
47
+ return localValue;
48
+ }
49
+ }
50
+
51
+ return value;
52
+ }
53
+
54
+ protected mapOptions(options: AjvValidateOptions | JsonSchema): AjvValidateOptions {
55
+ if (options instanceof JsonSchema) {
56
+ return {
57
+ schema: options.toJSON({customKeys: true})
58
+ };
59
+ }
60
+
61
+ return options;
62
+ }
63
+
64
+ protected mapErrors(errors: ErrorObject[], options: any) {
65
+ const {type, collectionType, value} = options;
66
+
67
+ const message = errors
68
+ .map((error: AjvErrorObject) => {
69
+ if (collectionType) {
70
+ error.collectionName = nameOf(collectionType);
71
+ }
72
+
73
+ const dataPath = getPath(error);
74
+
75
+ if (!error.data) {
76
+ if (dataPath) {
77
+ error.data = getValue(value, dataPath.replace(/^\./, ""));
78
+ } else if (error.schemaPath !== "#/required") {
79
+ error.data = value;
80
+ }
81
+ }
82
+
83
+ if (dataPath && dataPath.match(/pwd|password|mdp|secret/)) {
84
+ error.data = "[REDACTED]";
85
+ }
86
+
87
+ if (type) {
88
+ error.modelName = nameOf(type);
89
+ error.message = this.mapClassError(error, type);
90
+ }
91
+
92
+ return this.errorFormatter.call(this, error, {});
93
+ })
94
+ .join("\n");
95
+
96
+ return new AjvValidationError(message, errors);
97
+ }
98
+
99
+ protected mapClassError(error: AjvErrorObject, targetType: Type<any>) {
100
+ const propertyKey = getValue(error, "params.missingProperty");
101
+
102
+ if (propertyKey) {
103
+ const store = JsonEntityStore.from<JsonEntityStore>(prototypeOf(targetType), propertyKey);
104
+
105
+ if (store) {
106
+ setValue(error, "params.missingProperty", store.name || propertyKey);
107
+
108
+ return error.message?.replace(`'${propertyKey}'`, `'${store.name || propertyKey}'`);
109
+ }
110
+ }
111
+
112
+ return error.message;
113
+ }
114
+ }
115
+
116
+ injectable(AjvService).type("validator:service");
@@ -0,0 +1,73 @@
1
+ import {defaultErrorFormatter} from "./defaultErrorFormatter.js";
2
+
3
+ describe("defaultErrorFormatter", () => {
4
+ it("should extract data and format error (data = undefined)", () => {
5
+ expect(
6
+ defaultErrorFormatter({
7
+ keyword: "maximum",
8
+ dataPath: "/length",
9
+ schemaPath: "#/properties/length/maximum",
10
+ params: {comparison: "<=", limit: 250},
11
+ message: "should be <= 250",
12
+ data: undefined,
13
+ modelName: "RandomStringModel"
14
+ } as any)
15
+ ).toEqual('RandomStringModel.length should be <= 250. Given value: "undefined"');
16
+ });
17
+
18
+ it("should extract data and format error (modelName = '' & collectionName)", () => {
19
+ expect(
20
+ defaultErrorFormatter({
21
+ keyword: "maximum",
22
+ dataPath: "/0",
23
+ schemaPath: "#/properties/length/maximum",
24
+ params: {comparison: "<=", limit: 250},
25
+ message: "should be <= 250",
26
+ data: undefined,
27
+ modelName: undefined,
28
+ collectionName: "Array"
29
+ } as any)
30
+ ).toEqual('[0] should be <= 250. Given value: "undefined"');
31
+ });
32
+
33
+ it("should extract data and format error (modelName = '')", () => {
34
+ expect(
35
+ defaultErrorFormatter({
36
+ keyword: "maximum",
37
+ dataPath: "/length",
38
+ schemaPath: "#/properties/length/maximum",
39
+ params: {comparison: "<=", limit: 250},
40
+ message: "should be <= 250",
41
+ data: undefined,
42
+ modelName: undefined
43
+ } as any)
44
+ ).toEqual('Value.length should be <= 250. Given value: "undefined"');
45
+ });
46
+
47
+ it("should extract data and format error", () => {
48
+ expect(
49
+ defaultErrorFormatter({
50
+ keyword: "maximum",
51
+ dataPath: "/length",
52
+ schemaPath: "#/properties/length/maximum",
53
+ params: {comparison: "<=", limit: 250},
54
+ message: "should be <= 250",
55
+ data: 251,
56
+ modelName: "RandomStringModel"
57
+ } as any)
58
+ ).toEqual("RandomStringModel.length should be <= 250. Given value: 251");
59
+ });
60
+ it("should extract data and format error(2)", () => {
61
+ expect(
62
+ defaultErrorFormatter({
63
+ instancePath: "/length",
64
+ schemaPath: "#/properties/length/maximum",
65
+ keyword: "maximum",
66
+ params: {comparison: "<=", limit: 250},
67
+ message: "must be <= 250",
68
+ data: {length: 251},
69
+ modelName: "RandomStringModel"
70
+ } as any)
71
+ ).toEqual('RandomStringModel.length must be <= 250. Given value: {"length":251}');
72
+ });
73
+ });
@@ -0,0 +1,33 @@
1
+ import {AjvErrorObject} from "../interfaces/IAjvSettings.js";
2
+ import {getInstancePath, getPath} from "./getPath.js";
3
+
4
+ export function defaultErrorFormatter(error: AjvErrorObject & {dataPath: string}) {
5
+ const value = JSON.stringify(error.data === undefined ? "undefined" : error.data);
6
+ const join = (list: any[]): string => list.filter(Boolean).join("").trim();
7
+
8
+ const [, indexPath, ...paths] = getInstancePath(error).split(".");
9
+ const deepPaths = paths.length ? "." + paths.join(".") : "";
10
+
11
+ error.dataPath = getPath(error);
12
+
13
+ if (error.collectionName) {
14
+ const modelName = error.modelName || "";
15
+
16
+ switch (error.collectionName) {
17
+ case "Array":
18
+ return join([`${modelName}[${indexPath}]${deepPaths}`, ` ${error.message}. Given value: ${value}`]);
19
+ case "Map":
20
+ return join([`Map<${indexPath}, ${modelName}>${deepPaths}`, ` ${error.message}. Given value: ${value}`]);
21
+ case "Set":
22
+ return join([`Set<${indexPath}, ${modelName}>${deepPaths}`, ` ${error.message}. Given value: ${value}`]);
23
+ }
24
+ }
25
+
26
+ return join([
27
+ !error.modelName && "Value",
28
+ `${error.modelName || ""}`,
29
+ (!error?.params?.missingProperty && error.dataPath) ||
30
+ (error?.params?.missingProperty && (error.instancePath || "").replace(/\//g, ".")),
31
+ ` ${error.message}. Given value: ${value}`
32
+ ]);
33
+ }
@@ -0,0 +1,13 @@
1
+ import {getValue} from "@tsed/core";
2
+
3
+ export function getPath(error: any) {
4
+ if (error.params && error.params.missingProperty) {
5
+ return `.${error.params.missingProperty}`;
6
+ }
7
+
8
+ return getInstancePath(error);
9
+ }
10
+
11
+ export function getInstancePath(error: any) {
12
+ return getValue(error, "dataPath", getValue(error, "instancePath", "")).replace(/\//gi, ".");
13
+ }