1 | "use strict";
|
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
|
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
|
4 | };
|
5 | Object.defineProperty(exports, "__esModule", { value: true });
|
6 | exports.ErrorCodes = exports.ValidationError = exports.SchemerError = void 0;
|
7 | const ajv_1 = __importDefault(require("ajv"));
|
8 | const fs_1 = __importDefault(require("fs"));
|
9 | const json_schema_traverse_1 = __importDefault(require("json-schema-traverse"));
|
10 | const get_1 = __importDefault(require("lodash/get"));
|
11 | const path_1 = __importDefault(require("path"));
|
12 | const probe_image_size_1 = __importDefault(require("probe-image-size"));
|
13 | const read_chunk_1 = __importDefault(require("read-chunk"));
|
14 | const Error_1 = require("./Error");
|
15 | const Util_1 = require("./Util");
|
16 | var Error_2 = require("./Error");
|
17 | Object.defineProperty(exports, "SchemerError", { enumerable: true, get: function () { return Error_2.SchemerError; } });
|
18 | Object.defineProperty(exports, "ValidationError", { enumerable: true, get: function () { return Error_2.ValidationError; } });
|
19 | Object.defineProperty(exports, "ErrorCodes", { enumerable: true, get: function () { return Error_2.ErrorCodes; } });
|
20 | class Schemer {
|
21 |
|
22 | constructor(schema, options = {}) {
|
23 | this.options = {
|
24 | allErrors: true,
|
25 | verbose: true,
|
26 | format: 'full',
|
27 | metaValidation: true,
|
28 | ...options,
|
29 | };
|
30 | this.ajv = new ajv_1.default(this.options);
|
31 | this.schema = schema;
|
32 | this.rootDir = this.options.rootDir || __dirname;
|
33 | this.manualValidationErrors = [];
|
34 | }
|
35 | _formatAjvErrorMessage({ keyword, dataPath, params, parentSchema, data, message, }) {
|
36 | const meta = parentSchema && parentSchema.meta;
|
37 |
|
38 | dataPath = dataPath.slice(1);
|
39 | switch (keyword) {
|
40 | case 'additionalProperties': {
|
41 | return new Error_1.ValidationError({
|
42 | errorCode: 'SCHEMA_ADDITIONAL_PROPERTY',
|
43 | fieldPath: dataPath,
|
44 | message: `should NOT have additional property '${params.additionalProperty}'`,
|
45 | data,
|
46 | meta,
|
47 | });
|
48 | }
|
49 | case 'required':
|
50 | return new Error_1.ValidationError({
|
51 | errorCode: 'SCHEMA_MISSING_REQUIRED_PROPERTY',
|
52 | fieldPath: dataPath,
|
53 | message: `is missing required property '${params.missingProperty}'`,
|
54 | data,
|
55 | meta,
|
56 | });
|
57 | case 'pattern': {
|
58 |
|
59 | const regexHuman = meta === null || meta === void 0 ? void 0 : meta.regexHuman;
|
60 | const regexErrorMessage = regexHuman
|
61 | ? `'${dataPath}' should be a ${regexHuman[0].toLowerCase() + regexHuman.slice(1)}`
|
62 | : `'${dataPath}' ${message}`;
|
63 | return new Error_1.ValidationError({
|
64 | errorCode: 'SCHEMA_INVALID_PATTERN',
|
65 | fieldPath: dataPath,
|
66 | message: regexErrorMessage,
|
67 | data,
|
68 | meta,
|
69 | });
|
70 | }
|
71 | default:
|
72 | return new Error_1.ValidationError({
|
73 | errorCode: 'SCHEMA_VALIDATION_ERROR',
|
74 | fieldPath: dataPath,
|
75 | message: message || 'Validation error',
|
76 | data,
|
77 | meta,
|
78 | });
|
79 | }
|
80 | }
|
81 | getErrors() {
|
82 |
|
83 | let valErrors = [];
|
84 | if (this.ajv.errors) {
|
85 | valErrors = this.ajv.errors.map(e => this._formatAjvErrorMessage(e));
|
86 | }
|
87 | return [...valErrors, ...this.manualValidationErrors];
|
88 | }
|
89 | _throwOnErrors() {
|
90 |
|
91 | const errors = this.getErrors();
|
92 | if (errors.length > 0) {
|
93 | this.manualValidationErrors = [];
|
94 | this.ajv.errors = [];
|
95 | throw new Error_1.SchemerError(errors);
|
96 | }
|
97 | }
|
98 | async validateAll(data) {
|
99 | await this._validateSchemaAsync(data);
|
100 | await this._validateAssetsAsync(data);
|
101 | this._throwOnErrors();
|
102 | }
|
103 | async validateAssetsAsync(data) {
|
104 | await this._validateAssetsAsync(data);
|
105 | this._throwOnErrors();
|
106 | }
|
107 | async validateSchemaAsync(data) {
|
108 | await this._validateSchemaAsync(data);
|
109 | this._throwOnErrors();
|
110 | }
|
111 | _validateSchemaAsync(data) {
|
112 | this.ajv.validate(this.schema, data);
|
113 | }
|
114 | async _validateAssetsAsync(data) {
|
115 | const assets = [];
|
116 | json_schema_traverse_1.default(this.schema, { allKeys: true }, (subSchema, jsonPointer, a, b, c, d, property) => {
|
117 | if (property && subSchema.meta && subSchema.meta.asset) {
|
118 | const fieldPath = Util_1.schemaPointerToFieldPath(jsonPointer);
|
119 | assets.push({
|
120 | fieldPath,
|
121 | data: get_1.default(data, fieldPath),
|
122 | meta: subSchema.meta,
|
123 | });
|
124 | }
|
125 | });
|
126 | await Promise.all(assets.map(this._validateAssetAsync.bind(this)));
|
127 | }
|
128 | async _validateImageAsync({ fieldPath, data, meta }) {
|
129 | if (meta && meta.asset && data) {
|
130 | const { dimensions, square, contentTypePattern } = meta;
|
131 |
|
132 | const filePath = path_1.default.resolve(this.rootDir, data);
|
133 | try {
|
134 |
|
135 |
|
136 |
|
137 |
|
138 |
|
139 |
|
140 | const probeResult = fs_1.default.existsSync(filePath)
|
141 | ? probe_image_size_1.default.sync(await read_chunk_1.default(filePath, 0, 4100))
|
142 | : await probe_image_size_1.default(data, { useElectronNet: false });
|
143 | if (!probeResult) {
|
144 | return;
|
145 | }
|
146 | const { width, height, type, mime } = probeResult;
|
147 | if (contentTypePattern && !mime.match(new RegExp(contentTypePattern))) {
|
148 | this.manualValidationErrors.push(new Error_1.ValidationError({
|
149 | errorCode: 'INVALID_CONTENT_TYPE',
|
150 | fieldPath,
|
151 | message: `field '${fieldPath}' should point to ${meta.contentTypeHuman} but the file at '${data}' has type ${type}`,
|
152 | data,
|
153 | meta,
|
154 | }));
|
155 | }
|
156 | if (dimensions && (dimensions.height !== height || dimensions.width !== width)) {
|
157 | this.manualValidationErrors.push(new Error_1.ValidationError({
|
158 | errorCode: 'INVALID_DIMENSIONS',
|
159 | fieldPath,
|
160 | message: `'${fieldPath}' should have dimensions ${dimensions.width}x${dimensions.height}, but the file at '${data}' has dimensions ${width}x${height}`,
|
161 | data,
|
162 | meta,
|
163 | }));
|
164 | }
|
165 | if (square && width !== height) {
|
166 | this.manualValidationErrors.push(new Error_1.ValidationError({
|
167 | errorCode: 'NOT_SQUARE',
|
168 | fieldPath,
|
169 | message: `image should be square, but the file at '${data}' has dimensions ${width}x${height}`,
|
170 | data,
|
171 | meta,
|
172 | }));
|
173 | }
|
174 | }
|
175 | catch (e) {
|
176 | this.manualValidationErrors.push(new Error_1.ValidationError({
|
177 | errorCode: 'INVALID_ASSET_URI',
|
178 | fieldPath,
|
179 | message: `cannot access file at '${data}'`,
|
180 | data,
|
181 | meta,
|
182 | }));
|
183 | }
|
184 | }
|
185 | }
|
186 | async _validateAssetAsync({ fieldPath, data, meta }) {
|
187 | if (meta && meta.asset && data) {
|
188 | if (meta.contentTypePattern && meta.contentTypePattern.startsWith('^image')) {
|
189 | await this._validateImageAsync({ fieldPath, data, meta });
|
190 | }
|
191 | }
|
192 | }
|
193 | async validateProperty(fieldPath, data) {
|
194 | const subSchema = Util_1.fieldPathToSchema(this.schema, fieldPath);
|
195 | this.ajv.validate(subSchema, data);
|
196 | if (subSchema.meta && subSchema.meta.asset) {
|
197 | await this._validateAssetAsync({ fieldPath, data, meta: subSchema.meta });
|
198 | }
|
199 | this._throwOnErrors();
|
200 | }
|
201 | validateName(name) {
|
202 | return this.validateProperty('name', name);
|
203 | }
|
204 | validateSlug(slug) {
|
205 | return this.validateProperty('slug', slug);
|
206 | }
|
207 | validateSdkVersion(version) {
|
208 | return this.validateProperty('sdkVersion', version);
|
209 | }
|
210 | validateIcon(iconPath) {
|
211 | return this.validateProperty('icon', iconPath);
|
212 | }
|
213 | }
|
214 | exports.default = Schemer;
|
215 |
|
\ | No newline at end of file |