@expo/schemer 0.0.1-canary-20240109-93608d8

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017-present, 650 Industries
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,103 @@
1
+ <!-- Title -->
2
+ <h1 align="center">
3
+ 👋 Welcome to <br><code>@expo/schemer</code>
4
+ </h1>
5
+
6
+ <p align="center">A Schema validation library for Expo.</p>
7
+
8
+ <!-- Body -->
9
+
10
+ Details can be found here:
11
+ * https://paper.dropbox.com/doc/Expo-Schema-Validation-Library-mQU07rRejSnEe4Vf5dkcS
12
+
13
+ ## Usage
14
+
15
+ ### Usage with XDL
16
+
17
+ ```javascript
18
+ import { getConfig } from '@expo/config';
19
+ import Schemer from '@expo/schemer';
20
+
21
+ const { exp } = getConfig(projectRoot);
22
+ const schema = await getSchemaAsync(exp.sdkVersion);
23
+ const validator = new Schemer(require('schema.json'));
24
+
25
+ validator.validateName('Wilson Zhao');
26
+ validator.validateAssets(exp);
27
+ ```
28
+
29
+ ### Schema-only validation
30
+
31
+ ```javascript
32
+ const validator = new Schemer(require('schema.json'));
33
+ try {
34
+ await validator.validateSchemaAsync(require('data.json'));
35
+ } catch (e) {
36
+ console.error(e);
37
+ }
38
+ ```
39
+
40
+ ### Validating a property
41
+
42
+ ```javascript
43
+ const validator = new Schemer(require('schema.json'));
44
+ await validator.validateName('Wilson Zhao');
45
+ ```
46
+
47
+ ## Description
48
+
49
+ Schemer takes in a custom JSON Schema and uses it to validate various data.
50
+
51
+ Under the hood, it uses Ajv (https://github.com/epoberezkin/ajv) as the Javascript engine for basic schema validation.
52
+ However, each subschema also contains a custom meta tag, which can be parsed for further "manual" validation. As of now, Schemer supports manual validation for assets:
53
+
54
+ ```javascript
55
+ {
56
+ meta:
57
+ {
58
+ asset,
59
+ contentType, //mime type
60
+ dimensions: {width, height},
61
+ square,
62
+
63
+ // For custom error messages and docs
64
+ regexHuman,
65
+ autogenerated,
66
+ notHuman
67
+ }
68
+ }
69
+ ```
70
+
71
+ All errors can be accessed in `this.errors`, which has a getter function that combines Ajv JSON Schema errors with custom meta/asset validation errors into a unified array of `ValidationErrors`.
72
+ If they exist, the errors are thrown at the end of each public-facing function.
73
+
74
+ All public-facing functions are async functions because asset validation has to be async (accessing the file-system or making a web request).
75
+
76
+ ## API
77
+
78
+ #### new Schemer(Object JSON Schema, Object options) -> Object
79
+
80
+ #### .validateSchemaAsync(Object data) -> Promise
81
+
82
+ Returns a promise that resolve to `true` if the data is conforms to the schema. Otherwise, it rejects and throws an array of `ValidationError`s.
83
+
84
+ #### .validateAssetsAsync(Object data) -> Promise
85
+
86
+ Returns a promise that resolve to `true` if the data is conforms to the additional validation steps found in each meta tag. For example, it will download an asset and read the header of the file to see if it is a certain content type.
87
+ Otherwise, it rejects and throws an array of `ValidationError`s.
88
+
89
+ #### .validateAll(Object data) -> Promise
90
+
91
+ Runs both `.validateSchemaAsync` and `.validateAssetsAsync`.
92
+ Returns a promise that resolve to `true` if the data passes both functions. Otherwise, it rejects and throws an array of `ValidationError`s.
93
+
94
+ #### .validateProperty(String fieldPath, Object data) -> Promise
95
+
96
+ Extracts the subSchema for the given field path and validates the data against it. Also checks for the meta tag.
97
+ Returns a promise that resolve to `true` if the data conforms to the subschema. Otherwise, it rejects and throws an array of `ValidationError`s.
98
+
99
+ #### .errors
100
+
101
+ Contains an array of ValidationErrors
102
+
103
+ #### new ValidationError({errorCode, fieldPath, message, data, meta}) -> Object
@@ -0,0 +1,32 @@
1
+ export declare class SchemerError extends Error {
2
+ readonly name = "SchemerError";
3
+ errors: ValidationError[];
4
+ constructor(errors: ValidationError[]);
5
+ }
6
+ export declare class ValidationError extends Error {
7
+ readonly name = "ValidationError";
8
+ errorCode: string;
9
+ fieldPath: string;
10
+ data: any;
11
+ meta: any;
12
+ constructor({ errorCode, fieldPath, message, data, meta, }: {
13
+ errorCode: ErrorCode;
14
+ fieldPath: string;
15
+ message: string;
16
+ data: any;
17
+ meta: any;
18
+ });
19
+ }
20
+ export type ErrorCode = keyof typeof ErrorCodes;
21
+ export declare const ErrorCodes: {
22
+ SCHEMA_VALIDATION_ERROR: string;
23
+ SCHEMA_ADDITIONAL_PROPERTY: string;
24
+ SCHEMA_MISSING_REQUIRED_PROPERTY: string;
25
+ SCHEMA_INVALID_PATTERN: string;
26
+ SCHEMA_INVALID_NOT: string;
27
+ INVALID_ASSET_URI: string;
28
+ INVALID_DIMENSIONS: string;
29
+ INVALID_CONTENT_TYPE: string;
30
+ NOT_SQUARE: string;
31
+ FILE_EXTENSION_MISMATCH: string;
32
+ };
package/build/Error.js ADDED
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ErrorCodes = exports.ValidationError = exports.SchemerError = void 0;
4
+ class SchemerError extends Error {
5
+ name = 'SchemerError';
6
+ errors;
7
+ constructor(errors) {
8
+ super('');
9
+ this.message = errors.map((error) => error.message).join('\n');
10
+ this.errors = errors;
11
+ }
12
+ }
13
+ exports.SchemerError = SchemerError;
14
+ class ValidationError extends Error {
15
+ name = 'ValidationError';
16
+ errorCode;
17
+ fieldPath;
18
+ data;
19
+ meta;
20
+ constructor({ errorCode, fieldPath, message, data, meta, }) {
21
+ super(message);
22
+ this.errorCode = errorCode;
23
+ this.fieldPath = fieldPath;
24
+ this.data = data;
25
+ this.meta = meta;
26
+ }
27
+ }
28
+ exports.ValidationError = ValidationError;
29
+ exports.ErrorCodes = {
30
+ SCHEMA_VALIDATION_ERROR: 'SCHEMA_VALIDATION_ERROR',
31
+ SCHEMA_ADDITIONAL_PROPERTY: 'SCHEMA_ADDITIONAL_PROPERTY',
32
+ SCHEMA_MISSING_REQUIRED_PROPERTY: 'SCHEMA_MISSING_REQUIRED_PROPERTY',
33
+ SCHEMA_INVALID_PATTERN: 'SCHEMA_INVALID_PATTERN',
34
+ SCHEMA_INVALID_NOT: 'SCHEMA_INVALID_NOT',
35
+ INVALID_ASSET_URI: 'INVALID_ASSET_URI',
36
+ INVALID_DIMENSIONS: 'INVALID_DIMENSIONS',
37
+ INVALID_CONTENT_TYPE: 'INVALID_CONTENT_TYPE',
38
+ NOT_SQUARE: 'NOT_SQUARE',
39
+ FILE_EXTENSION_MISMATCH: 'FILE_EXTENSION_MISMATCH',
40
+ };
41
+ //# sourceMappingURL=Error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Error.js","sourceRoot":"","sources":["../src/Error.ts"],"names":[],"mappings":";;;AAAA,MAAa,YAAa,SAAQ,KAAK;IAC5B,IAAI,GAAG,cAAc,CAAC;IAC/B,MAAM,CAAoB;IAE1B,YAAY,MAAyB;QACnC,KAAK,CAAC,EAAE,CAAC,CAAC;QACV,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/D,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AATD,oCASC;AAED,MAAa,eAAgB,SAAQ,KAAK;IAC/B,IAAI,GAAG,iBAAiB,CAAC;IAClC,SAAS,CAAS;IAClB,SAAS,CAAS;IAClB,IAAI,CAAM;IACV,IAAI,CAAM;IACV,YAAY,EACV,SAAS,EACT,SAAS,EACT,OAAO,EACP,IAAI,EACJ,IAAI,GAOL;QACC,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAzBD,0CAyBC;AAIY,QAAA,UAAU,GAAG;IACxB,uBAAuB,EAAE,yBAAyB;IAClD,0BAA0B,EAAE,4BAA4B;IACxD,gCAAgC,EAAE,kCAAkC;IACpE,sBAAsB,EAAE,wBAAwB;IAChD,kBAAkB,EAAE,oBAAoB;IAExC,iBAAiB,EAAE,mBAAmB;IACtC,kBAAkB,EAAE,oBAAoB;IACxC,oBAAoB,EAAE,sBAAsB;IAC5C,UAAU,EAAE,YAAY;IACxB,uBAAuB,EAAE,yBAAyB;CACnD,CAAC"}
@@ -0,0 +1,5 @@
1
+ export declare const fieldPathToSchemaPath: (fieldPath: string) => string;
2
+ export declare const schemaPointerToFieldPath: (jsonPointer: string) => string;
3
+ export declare const fieldPathToSchema: (schema: object, fieldPath: string) => any;
4
+ export declare function pathToSegments(path: string | string[]): string[];
5
+ export declare function get(object: any, path: string | string[]): any;
package/build/Util.js ADDED
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.get = exports.pathToSegments = exports.fieldPathToSchema = exports.schemaPointerToFieldPath = exports.fieldPathToSchemaPath = void 0;
4
+ const fieldPathToSchemaPath = (fieldPath) => {
5
+ return pathToSegments(fieldPath)
6
+ .map((segment) => `properties.${segment}`)
7
+ .join('.');
8
+ };
9
+ exports.fieldPathToSchemaPath = fieldPathToSchemaPath;
10
+ // Assumption: used only for jsonPointer returned from traverse
11
+ const schemaPointerToFieldPath = (jsonPointer) => {
12
+ return jsonPointer
13
+ .split('/')
14
+ .slice(2)
15
+ .filter((error) => error !== 'properties')
16
+ .join('.');
17
+ };
18
+ exports.schemaPointerToFieldPath = schemaPointerToFieldPath;
19
+ const fieldPathToSchema = (schema, fieldPath) => {
20
+ return get(schema, (0, exports.fieldPathToSchemaPath)(fieldPath));
21
+ };
22
+ exports.fieldPathToSchema = fieldPathToSchema;
23
+ function pathToSegments(path) {
24
+ return Array.isArray(path) ? path : path.split('.');
25
+ }
26
+ exports.pathToSegments = pathToSegments;
27
+ function get(object, path) {
28
+ const segments = pathToSegments(path);
29
+ const length = segments.length;
30
+ let index = 0;
31
+ while (object != null && index < length) {
32
+ object = object[segments[index++]];
33
+ }
34
+ return index && index === length ? object : undefined;
35
+ }
36
+ exports.get = get;
37
+ //# sourceMappingURL=Util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Util.js","sourceRoot":"","sources":["../src/Util.ts"],"names":[],"mappings":";;;AAAO,MAAM,qBAAqB,GAAG,CAAC,SAAiB,EAAE,EAAE;IACzD,OAAO,cAAc,CAAC,SAAS,CAAC;SAC7B,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,cAAc,OAAO,EAAE,CAAC;SACzC,IAAI,CAAC,GAAG,CAAC,CAAC;AACf,CAAC,CAAC;AAJW,QAAA,qBAAqB,yBAIhC;AACF,+DAA+D;AACxD,MAAM,wBAAwB,GAAG,CAAC,WAAmB,EAAE,EAAE;IAC9D,OAAO,WAAW;SACf,KAAK,CAAC,GAAG,CAAC;SACV,KAAK,CAAC,CAAC,CAAC;SACR,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,YAAY,CAAC;SACzC,IAAI,CAAC,GAAG,CAAC,CAAC;AACf,CAAC,CAAC;AANW,QAAA,wBAAwB,4BAMnC;AAEK,MAAM,iBAAiB,GAAG,CAAC,MAAc,EAAE,SAAiB,EAAE,EAAE;IACrE,OAAO,GAAG,CAAC,MAAM,EAAE,IAAA,6BAAqB,EAAC,SAAS,CAAC,CAAC,CAAC;AACvD,CAAC,CAAC;AAFW,QAAA,iBAAiB,qBAE5B;AAEF,SAAgB,cAAc,CAAC,IAAuB;IACpD,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;AACtD,CAAC;AAFD,wCAEC;AAED,SAAgB,GAAG,CAAC,MAAW,EAAE,IAAuB;IACtD,MAAM,QAAQ,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACtC,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC;IAC/B,IAAI,KAAK,GAAG,CAAC,CAAC;IAEd,OAAO,MAAM,IAAI,IAAI,IAAI,KAAK,GAAG,MAAM,EAAE;QACvC,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;KACpC;IAED,OAAO,KAAK,IAAI,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;AACxD,CAAC;AAVD,kBAUC"}
@@ -0,0 +1,44 @@
1
+ import Ajv, { ErrorObject, Options } from 'ajv';
2
+ import { ValidationError } from './Error';
3
+ type Meta = {
4
+ asset?: boolean;
5
+ dimensions?: {
6
+ width: number;
7
+ height: number;
8
+ };
9
+ square?: boolean;
10
+ contentTypePattern?: string;
11
+ contentTypeHuman?: string;
12
+ };
13
+ type SchemerOptions = Options & {
14
+ rootDir?: string;
15
+ };
16
+ type AssetField = {
17
+ fieldPath: string;
18
+ data: string;
19
+ meta: Meta;
20
+ };
21
+ export { SchemerError, ValidationError, ErrorCodes, ErrorCode } from './Error';
22
+ export default class Schemer {
23
+ options: SchemerOptions;
24
+ ajv: Ajv;
25
+ schema: object;
26
+ rootDir: string;
27
+ manualValidationErrors: ValidationError[];
28
+ constructor(schema: object, options?: SchemerOptions);
29
+ _formatAjvErrorMessage({ keyword, instancePath, params, parentSchema, data, message, }: ErrorObject): ValidationError;
30
+ getErrors(): ValidationError[];
31
+ _throwOnErrors(): void;
32
+ validateAll(data: any): Promise<void>;
33
+ validateAssetsAsync(data: any): Promise<void>;
34
+ validateSchemaAsync(data: any): Promise<void>;
35
+ _validateSchemaAsync(data: any): void;
36
+ _validateAssetsAsync(data: any): Promise<void>;
37
+ _validateImageAsync({ fieldPath, data, meta }: AssetField): Promise<void>;
38
+ _validateAssetAsync({ fieldPath, data, meta }: AssetField): Promise<void>;
39
+ validateProperty(fieldPath: string, data: any): Promise<void>;
40
+ validateName(name: string): Promise<void>;
41
+ validateSlug(slug: string): Promise<void>;
42
+ validateSdkVersion(version: string): Promise<void>;
43
+ validateIcon(iconPath: string): Promise<void>;
44
+ }
package/build/index.js ADDED
@@ -0,0 +1,243 @@
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 path_1 = __importDefault(require("path"));
12
+ const probe_image_size_1 = __importDefault(require("probe-image-size"));
13
+ const Error_1 = require("./Error");
14
+ const Util_1 = require("./Util");
15
+ function lowerFirst(str) {
16
+ return str.charAt(0).toLowerCase() + str.slice(1);
17
+ }
18
+ var Error_2 = require("./Error");
19
+ Object.defineProperty(exports, "SchemerError", { enumerable: true, get: function () { return Error_2.SchemerError; } });
20
+ Object.defineProperty(exports, "ValidationError", { enumerable: true, get: function () { return Error_2.ValidationError; } });
21
+ Object.defineProperty(exports, "ErrorCodes", { enumerable: true, get: function () { return Error_2.ErrorCodes; } });
22
+ class Schemer {
23
+ options;
24
+ ajv;
25
+ schema;
26
+ rootDir;
27
+ manualValidationErrors;
28
+ // Schema is a JSON Schema object
29
+ constructor(schema, options = {}) {
30
+ this.options = {
31
+ allErrors: true,
32
+ verbose: true,
33
+ meta: true,
34
+ strict: false,
35
+ unicodeRegExp: false,
36
+ ...options,
37
+ };
38
+ this.ajv = new ajv_1.default(this.options);
39
+ (0, ajv_formats_1.default)(this.ajv, { mode: 'full' });
40
+ this.schema = schema;
41
+ this.rootDir = this.options.rootDir || __dirname;
42
+ this.manualValidationErrors = [];
43
+ }
44
+ _formatAjvErrorMessage({ keyword, instancePath, params, parentSchema, data, message, }) {
45
+ const meta = parentSchema && parentSchema.meta;
46
+ // This removes the "." in front of a fieldPath
47
+ instancePath = instancePath.slice(1);
48
+ switch (keyword) {
49
+ case 'additionalProperties': {
50
+ return new Error_1.ValidationError({
51
+ errorCode: 'SCHEMA_ADDITIONAL_PROPERTY',
52
+ fieldPath: instancePath,
53
+ message: `should NOT have additional property '${params.additionalProperty}'`,
54
+ data,
55
+ meta,
56
+ });
57
+ }
58
+ case 'required':
59
+ return new Error_1.ValidationError({
60
+ errorCode: 'SCHEMA_MISSING_REQUIRED_PROPERTY',
61
+ fieldPath: instancePath,
62
+ message: `is missing required property '${params.missingProperty}'`,
63
+ data,
64
+ meta,
65
+ });
66
+ case 'pattern': {
67
+ //@TODO Parse the message in a less hacky way. Perhaps for regex validation errors, embed the error message under the meta tag?
68
+ const regexHuman = meta?.regexHuman;
69
+ const regexErrorMessage = regexHuman
70
+ ? `'${instancePath}' should be a ${regexHuman[0].toLowerCase() + regexHuman.slice(1)}`
71
+ : `'${instancePath}' ${message}`;
72
+ return new Error_1.ValidationError({
73
+ errorCode: 'SCHEMA_INVALID_PATTERN',
74
+ fieldPath: instancePath,
75
+ message: regexErrorMessage,
76
+ data,
77
+ meta,
78
+ });
79
+ }
80
+ case 'not': {
81
+ const notHuman = meta?.notHuman;
82
+ const notHumanErrorMessage = notHuman
83
+ ? `'${instancePath}' should be ${notHuman[0].toLowerCase() + notHuman.slice(1)}`
84
+ : `'${instancePath}' ${message}`;
85
+ return new Error_1.ValidationError({
86
+ errorCode: 'SCHEMA_INVALID_NOT',
87
+ fieldPath: instancePath,
88
+ message: notHumanErrorMessage,
89
+ data,
90
+ meta,
91
+ });
92
+ }
93
+ default:
94
+ return new Error_1.ValidationError({
95
+ errorCode: 'SCHEMA_VALIDATION_ERROR',
96
+ fieldPath: instancePath,
97
+ message: message || 'Validation error',
98
+ data,
99
+ meta,
100
+ });
101
+ }
102
+ }
103
+ getErrors() {
104
+ // Convert AJV JSONSchema errors to our ValidationErrors
105
+ let valErrors = [];
106
+ if (this.ajv.errors) {
107
+ valErrors = this.ajv.errors.map((error) => this._formatAjvErrorMessage(error));
108
+ }
109
+ return [...valErrors, ...this.manualValidationErrors];
110
+ }
111
+ _throwOnErrors() {
112
+ // Clean error state after each validation
113
+ const errors = this.getErrors();
114
+ if (errors.length > 0) {
115
+ this.manualValidationErrors = [];
116
+ this.ajv.errors = [];
117
+ throw new Error_1.SchemerError(errors);
118
+ }
119
+ }
120
+ async validateAll(data) {
121
+ await this._validateSchemaAsync(data);
122
+ await this._validateAssetsAsync(data);
123
+ this._throwOnErrors();
124
+ }
125
+ async validateAssetsAsync(data) {
126
+ await this._validateAssetsAsync(data);
127
+ this._throwOnErrors();
128
+ }
129
+ async validateSchemaAsync(data) {
130
+ await this._validateSchemaAsync(data);
131
+ this._throwOnErrors();
132
+ }
133
+ _validateSchemaAsync(data) {
134
+ this.ajv.validate(this.schema, data);
135
+ }
136
+ async _validateAssetsAsync(data) {
137
+ const assets = [];
138
+ (0, json_schema_traverse_1.default)(this.schema, { allKeys: true }, (subSchema, jsonPointer, a, b, c, d, property) => {
139
+ if (property && subSchema.meta && subSchema.meta.asset) {
140
+ const fieldPath = (0, Util_1.schemaPointerToFieldPath)(jsonPointer);
141
+ assets.push({
142
+ fieldPath,
143
+ data: (0, Util_1.get)(data, lowerFirst(fieldPath)) || (0, Util_1.get)(data, fieldPath),
144
+ meta: subSchema.meta,
145
+ });
146
+ }
147
+ });
148
+ await Promise.all(assets.map(this._validateAssetAsync.bind(this)));
149
+ }
150
+ async _validateImageAsync({ fieldPath, data, meta }) {
151
+ if (meta && meta.asset && data) {
152
+ const { dimensions, square, contentTypePattern } = meta;
153
+ // filePath could be an URL
154
+ const filePath = path_1.default.resolve(this.rootDir, data);
155
+ try {
156
+ // This cases on whether filePath is a remote URL or located on the machine
157
+ const isLocalFile = fs_1.default.existsSync(filePath);
158
+ const probeResult = isLocalFile
159
+ ? await (0, probe_image_size_1.default)(require('fs').createReadStream(filePath))
160
+ : await (0, probe_image_size_1.default)(data);
161
+ if (!probeResult) {
162
+ return;
163
+ }
164
+ const { width, height, type, mime } = probeResult;
165
+ const fileExtension = filePath.split('.').pop();
166
+ if (isLocalFile && mime !== `image/${fileExtension}`) {
167
+ this.manualValidationErrors.push(new Error_1.ValidationError({
168
+ errorCode: 'FILE_EXTENSION_MISMATCH',
169
+ fieldPath,
170
+ message: `the file extension should match the content, but the file extension is .${fileExtension} while the file content at '${data}' is of type ${type}`,
171
+ data,
172
+ meta,
173
+ }));
174
+ }
175
+ if (contentTypePattern && !mime.match(new RegExp(contentTypePattern))) {
176
+ this.manualValidationErrors.push(new Error_1.ValidationError({
177
+ errorCode: 'INVALID_CONTENT_TYPE',
178
+ fieldPath,
179
+ message: `field '${fieldPath}' should point to ${meta.contentTypeHuman} but the file at '${data}' has type ${type}`,
180
+ data,
181
+ meta,
182
+ }));
183
+ }
184
+ if (dimensions && (dimensions.height !== height || dimensions.width !== width)) {
185
+ this.manualValidationErrors.push(new Error_1.ValidationError({
186
+ errorCode: 'INVALID_DIMENSIONS',
187
+ fieldPath,
188
+ message: `'${fieldPath}' should have dimensions ${dimensions.width}x${dimensions.height}, but the file at '${data}' has dimensions ${width}x${height}`,
189
+ data,
190
+ meta,
191
+ }));
192
+ }
193
+ if (square && width !== height) {
194
+ this.manualValidationErrors.push(new Error_1.ValidationError({
195
+ errorCode: 'NOT_SQUARE',
196
+ fieldPath,
197
+ message: `image should be square, but the file at '${data}' has dimensions ${width}x${height}`,
198
+ data,
199
+ meta,
200
+ }));
201
+ }
202
+ }
203
+ catch {
204
+ this.manualValidationErrors.push(new Error_1.ValidationError({
205
+ errorCode: 'INVALID_ASSET_URI',
206
+ fieldPath,
207
+ message: `cannot access file at '${data}'`,
208
+ data,
209
+ meta,
210
+ }));
211
+ }
212
+ }
213
+ }
214
+ async _validateAssetAsync({ fieldPath, data, meta }) {
215
+ if (meta && meta.asset && data) {
216
+ if (meta.contentTypePattern && meta.contentTypePattern.startsWith('^image')) {
217
+ await this._validateImageAsync({ fieldPath, data, meta });
218
+ }
219
+ }
220
+ }
221
+ async validateProperty(fieldPath, data) {
222
+ const subSchema = (0, Util_1.fieldPathToSchema)(this.schema, fieldPath);
223
+ this.ajv.validate(subSchema, data);
224
+ if (subSchema.meta && subSchema.meta.asset) {
225
+ await this._validateAssetAsync({ fieldPath, data, meta: subSchema.meta });
226
+ }
227
+ this._throwOnErrors();
228
+ }
229
+ validateName(name) {
230
+ return this.validateProperty('name', name);
231
+ }
232
+ validateSlug(slug) {
233
+ return this.validateProperty('slug', slug);
234
+ }
235
+ validateSdkVersion(version) {
236
+ return this.validateProperty('sdkVersion', version);
237
+ }
238
+ validateIcon(iconPath) {
239
+ return this.validateProperty('icon', iconPath);
240
+ }
241
+ }
242
+ exports.default = Schemer;
243
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@expo/schemer",
3
+ "version": "0.0.1-canary-20240109-93608d8",
4
+ "description": "Centralized scheme validation library for Expo",
5
+ "license": "MIT",
6
+ "main": "./build/index.js",
7
+ "types": "./build/index.d.ts",
8
+ "files": [
9
+ "build"
10
+ ],
11
+ "scripts": {
12
+ "build": "expo-module tsc",
13
+ "prepare": "yarn run clean && yarn run build",
14
+ "clean": "expo-module clean",
15
+ "lint": "expo-module lint",
16
+ "test": "expo-module test",
17
+ "test:e2e": "expo-module test --config e2e/jest.config.js",
18
+ "typecheck": "expo-module typecheck",
19
+ "watch": "yarn run --watch --preserveWatchOutput",
20
+ "prepublishOnly": "expo-module prepublishOnly"
21
+ },
22
+ "homepage": "https://github.com/expo/expo/tree/main/packages/@expo/schemer#readme",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/expo/expo.git",
26
+ "directory": "packages/@expo/schemer"
27
+ },
28
+ "bugs": {
29
+ "url": "https://github.com/expo/expo/issues"
30
+ },
31
+ "dependencies": {
32
+ "ajv": "^8.1.0",
33
+ "ajv-formats": "^2.0.2",
34
+ "json-schema-traverse": "^1.0.0",
35
+ "probe-image-size": "^7.1.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/probe-image-size": "^7.2.4",
39
+ "expo-module-scripts": "0.0.1-canary-20240109-93608d8"
40
+ },
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
44
+ "gitHead": "93608d8dcb0268312e0c8ed22036ebfa6efe9830"
45
+ }