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