471 lines
16 KiB
JavaScript
471 lines
16 KiB
JavaScript
'use strict';
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
exports.ErrorCodes = exports.ValidationError = exports.SchemerError = undefined;
|
|
|
|
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
|
|
|
|
var _Error = require('./Error');
|
|
|
|
Object.defineProperty(exports, 'SchemerError', {
|
|
enumerable: true,
|
|
get: function get() {
|
|
return _Error.SchemerError;
|
|
}
|
|
});
|
|
Object.defineProperty(exports, 'ValidationError', {
|
|
enumerable: true,
|
|
get: function get() {
|
|
return _Error.ValidationError;
|
|
}
|
|
});
|
|
Object.defineProperty(exports, 'ErrorCodes', {
|
|
enumerable: true,
|
|
get: function get() {
|
|
return _Error.ErrorCodes;
|
|
}
|
|
});
|
|
|
|
require('babel-polyfill');
|
|
|
|
require('instapromise');
|
|
|
|
var _lodash = require('lodash');
|
|
|
|
var _lodash2 = _interopRequireDefault(_lodash);
|
|
|
|
var _ajv = require('ajv');
|
|
|
|
var _ajv2 = _interopRequireDefault(_ajv);
|
|
|
|
var _path = require('path');
|
|
|
|
var _path2 = _interopRequireDefault(_path);
|
|
|
|
var _fs = require('fs');
|
|
|
|
var _fs2 = _interopRequireDefault(_fs);
|
|
|
|
var _jsonSchemaTraverse = require('json-schema-traverse');
|
|
|
|
var _jsonSchemaTraverse2 = _interopRequireDefault(_jsonSchemaTraverse);
|
|
|
|
var _readChunk = require('read-chunk');
|
|
|
|
var _readChunk2 = _interopRequireDefault(_readChunk);
|
|
|
|
var _probeImageSize = require('probe-image-size');
|
|
|
|
var _probeImageSize2 = _interopRequireDefault(_probeImageSize);
|
|
|
|
var _Util = require('./Util');
|
|
|
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
|
|
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
|
|
|
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
|
|
var Schemer = function () {
|
|
// Schema is a JSON Schema object
|
|
function Schemer(schema) {
|
|
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
|
|
_classCallCheck(this, Schemer);
|
|
|
|
this.options = _lodash2.default.extend({
|
|
allErrors: true,
|
|
verbose: true,
|
|
format: 'full',
|
|
metaValidation: true
|
|
}, options);
|
|
|
|
this.ajv = new _ajv2.default(this.options);
|
|
this.schema = schema;
|
|
this.rootDir = this.options.rootDir || __dirname;
|
|
this.manualValidationErrors = [];
|
|
}
|
|
|
|
_createClass(Schemer, [{
|
|
key: '_formatAjvErrorMessage',
|
|
value: function _formatAjvErrorMessage(_ref) {
|
|
var keyword = _ref.keyword,
|
|
dataPath = _ref.dataPath,
|
|
params = _ref.params,
|
|
parentSchema = _ref.parentSchema,
|
|
data = _ref.data,
|
|
message = _ref.message;
|
|
|
|
// This removes the "." in front of a fieldPath
|
|
dataPath = dataPath.slice(1);
|
|
switch (keyword) {
|
|
case 'additionalProperties':
|
|
return new _Error.ValidationError({
|
|
errorCode: _Error.ErrorCodes.SCHEMA_ADDITIONAL_PROPERTY,
|
|
fieldPath: dataPath,
|
|
message: 'should NOT have additional property \'' + params.additionalProperty + '\'',
|
|
data: data,
|
|
meta: parentSchema.meta
|
|
});
|
|
case 'required':
|
|
return new _Error.ValidationError({
|
|
errorCode: _Error.ErrorCodes.SCHEMA_MISSING_REQUIRED_PROPERTY,
|
|
fieldPath: dataPath,
|
|
message: 'is missing required property \'' + params.missingProperty + '\'',
|
|
data: data,
|
|
meta: parentSchema.meta
|
|
});
|
|
case 'pattern':
|
|
//@TODO Parse the message in a less hacky way. Perhaps for regex validation errors, embed the error message under the meta tag?
|
|
var regexHuman = _lodash2.default.get(parentSchema, 'meta.regexHuman');
|
|
var regexErrorMessage = regexHuman ? '\'' + dataPath + '\' should be a ' + (regexHuman[0].toLowerCase() + regexHuman.slice(1)) : '\'' + dataPath + '\' ' + message;
|
|
return new _Error.ValidationError({
|
|
errorCode: _Error.ErrorCodes.SCHEMA_INVALID_PATTERN,
|
|
fieldPath: dataPath,
|
|
message: regexErrorMessage,
|
|
data: data,
|
|
meta: parentSchema.meta
|
|
});
|
|
default:
|
|
return new _Error.ValidationError({
|
|
errorCode: _Error.ErrorCodes.SCHEMA_VALIDATION_ERROR,
|
|
fieldPath: dataPath,
|
|
message: message,
|
|
data: data,
|
|
meta: parentSchema.meta
|
|
});
|
|
}
|
|
}
|
|
}, {
|
|
key: 'getErrors',
|
|
value: function getErrors() {
|
|
var _this = this;
|
|
|
|
// Convert AJV JSONSchema errors to our ValidationErrors
|
|
var valErrors = [];
|
|
if (this.ajv.errors) {
|
|
valErrors = this.ajv.errors.map(function (e) {
|
|
return _this._formatAjvErrorMessage(e);
|
|
});
|
|
}
|
|
var bothErrors = _lodash2.default.concat(valErrors, this.manualValidationErrors);
|
|
return bothErrors;
|
|
}
|
|
}, {
|
|
key: '_throwOnErrors',
|
|
value: function _throwOnErrors() {
|
|
// Clean error state after each validation
|
|
var errors = this.getErrors();
|
|
if (errors.length > 0) {
|
|
this.manualValidationErrors = [];
|
|
this.ajv.errors = [];
|
|
throw new _Error.SchemerError(errors);
|
|
}
|
|
}
|
|
}, {
|
|
key: 'validateAll',
|
|
value: function () {
|
|
var _ref2 = _asyncToGenerator(regeneratorRuntime.mark(function _callee(data) {
|
|
return regeneratorRuntime.wrap(function _callee$(_context) {
|
|
while (1) {
|
|
switch (_context.prev = _context.next) {
|
|
case 0:
|
|
_context.next = 2;
|
|
return this._validateSchemaAsync(data);
|
|
|
|
case 2:
|
|
_context.next = 4;
|
|
return this._validateAssetsAsync(data);
|
|
|
|
case 4:
|
|
this._throwOnErrors();
|
|
|
|
case 5:
|
|
case 'end':
|
|
return _context.stop();
|
|
}
|
|
}
|
|
}, _callee, this);
|
|
}));
|
|
|
|
function validateAll(_x2) {
|
|
return _ref2.apply(this, arguments);
|
|
}
|
|
|
|
return validateAll;
|
|
}()
|
|
}, {
|
|
key: 'validateAssetsAsync',
|
|
value: function () {
|
|
var _ref3 = _asyncToGenerator(regeneratorRuntime.mark(function _callee2(data) {
|
|
return regeneratorRuntime.wrap(function _callee2$(_context2) {
|
|
while (1) {
|
|
switch (_context2.prev = _context2.next) {
|
|
case 0:
|
|
_context2.next = 2;
|
|
return this._validateAssetsAsync(data);
|
|
|
|
case 2:
|
|
this._throwOnErrors();
|
|
|
|
case 3:
|
|
case 'end':
|
|
return _context2.stop();
|
|
}
|
|
}
|
|
}, _callee2, this);
|
|
}));
|
|
|
|
function validateAssetsAsync(_x3) {
|
|
return _ref3.apply(this, arguments);
|
|
}
|
|
|
|
return validateAssetsAsync;
|
|
}()
|
|
}, {
|
|
key: 'validateSchemaAsync',
|
|
value: function () {
|
|
var _ref4 = _asyncToGenerator(regeneratorRuntime.mark(function _callee3(data) {
|
|
return regeneratorRuntime.wrap(function _callee3$(_context3) {
|
|
while (1) {
|
|
switch (_context3.prev = _context3.next) {
|
|
case 0:
|
|
_context3.next = 2;
|
|
return this._validateSchemaAsync(data);
|
|
|
|
case 2:
|
|
this._throwOnErrors();
|
|
|
|
case 3:
|
|
case 'end':
|
|
return _context3.stop();
|
|
}
|
|
}
|
|
}, _callee3, this);
|
|
}));
|
|
|
|
function validateSchemaAsync(_x4) {
|
|
return _ref4.apply(this, arguments);
|
|
}
|
|
|
|
return validateSchemaAsync;
|
|
}()
|
|
}, {
|
|
key: '_validateSchemaAsync',
|
|
value: function _validateSchemaAsync(data) {
|
|
this.ajv.validate(this.schema, data);
|
|
}
|
|
}, {
|
|
key: '_validateAssetsAsync',
|
|
value: function () {
|
|
var _ref5 = _asyncToGenerator(regeneratorRuntime.mark(function _callee4(data) {
|
|
var assets;
|
|
return regeneratorRuntime.wrap(function _callee4$(_context4) {
|
|
while (1) {
|
|
switch (_context4.prev = _context4.next) {
|
|
case 0:
|
|
assets = [];
|
|
|
|
(0, _jsonSchemaTraverse2.default)(this.schema, { allKeys: true }, function (subSchema, jsonPointer, a, b, c, d, property) {
|
|
if (property && subSchema.meta && subSchema.meta.asset) {
|
|
var fieldPath = (0, _Util.schemaPointerToFieldPath)(jsonPointer);
|
|
assets.push({
|
|
fieldPath: fieldPath,
|
|
data: _lodash2.default.get(data, fieldPath),
|
|
meta: subSchema.meta
|
|
});
|
|
}
|
|
});
|
|
_context4.next = 4;
|
|
return Promise.all(assets.map(this._validateAssetAsync.bind(this)));
|
|
|
|
case 4:
|
|
case 'end':
|
|
return _context4.stop();
|
|
}
|
|
}
|
|
}, _callee4, this);
|
|
}));
|
|
|
|
function _validateAssetsAsync(_x5) {
|
|
return _ref5.apply(this, arguments);
|
|
}
|
|
|
|
return _validateAssetsAsync;
|
|
}()
|
|
}, {
|
|
key: '_validateAssetAsync',
|
|
value: function () {
|
|
var _ref7 = _asyncToGenerator(regeneratorRuntime.mark(function _callee5(_ref6) {
|
|
var fieldPath = _ref6.fieldPath,
|
|
data = _ref6.data,
|
|
meta = _ref6.meta;
|
|
|
|
var _asset, _dimensions, _square, _contentTypePattern, _contentTypeHuman, filePath, probeResult, width, height, type, mime, wUnits, hUnits;
|
|
|
|
return regeneratorRuntime.wrap(function _callee5$(_context5) {
|
|
while (1) {
|
|
switch (_context5.prev = _context5.next) {
|
|
case 0:
|
|
if (!(meta && meta.asset && data)) {
|
|
_context5.next = 25;
|
|
break;
|
|
}
|
|
|
|
_asset = meta.asset, _dimensions = meta.dimensions, _square = meta.square, _contentTypePattern = meta.contentTypePattern, _contentTypeHuman = meta.contentTypeHuman;
|
|
// filePath could be an URL
|
|
|
|
filePath = _path2.default.resolve(this.rootDir, data);
|
|
_context5.prev = 3;
|
|
|
|
if (!_fs2.default.existsSync(filePath)) {
|
|
_context5.next = 12;
|
|
break;
|
|
}
|
|
|
|
_context5.t1 = _probeImageSize2.default;
|
|
_context5.next = 8;
|
|
return (0, _readChunk2.default)(filePath, 0, 4100);
|
|
|
|
case 8:
|
|
_context5.t2 = _context5.sent;
|
|
_context5.t0 = _context5.t1.sync.call(_context5.t1, _context5.t2);
|
|
_context5.next = 15;
|
|
break;
|
|
|
|
case 12:
|
|
_context5.next = 14;
|
|
return (0, _probeImageSize2.default)(data, { useElectronNet: false });
|
|
|
|
case 14:
|
|
_context5.t0 = _context5.sent;
|
|
|
|
case 15:
|
|
probeResult = _context5.t0;
|
|
width = probeResult.width, height = probeResult.height, type = probeResult.type, mime = probeResult.mime, wUnits = probeResult.wUnits, hUnits = probeResult.hUnits;
|
|
|
|
|
|
if (_contentTypePattern && !mime.match(new RegExp(_contentTypePattern))) {
|
|
this.manualValidationErrors.push(new _Error.ValidationError({
|
|
errorCode: _Error.ErrorCodes.INVALID_CONTENT_TYPE,
|
|
fieldPath: fieldPath,
|
|
message: 'field \'' + fieldPath + '\' should point to ' + meta.contentTypeHuman + ' but the file at \'' + data + '\' has type ' + type,
|
|
data: data,
|
|
meta: meta
|
|
}));
|
|
}
|
|
|
|
if (_dimensions && (_dimensions.height !== height || _dimensions.width != width)) {
|
|
this.manualValidationErrors.push(new _Error.ValidationError({
|
|
errorCode: _Error.ErrorCodes.INVALID_DIMENSIONS,
|
|
fieldPath: fieldPath,
|
|
message: '\'' + fieldPath + '\' should have dimensions ' + _dimensions.width + 'x' + _dimensions.height + ', but the file at \'' + data + '\' has dimensions ' + width + 'x' + height,
|
|
data: data,
|
|
meta: meta
|
|
}));
|
|
}
|
|
|
|
if (_square && width !== height) {
|
|
this.manualValidationErrors.push(new _Error.ValidationError({
|
|
errorCode: _Error.ErrorCodes.NOT_SQUARE,
|
|
fieldPath: fieldPath,
|
|
message: 'image should be square, but the file at \'' + data + '\' has dimensions ' + width + 'x' + height,
|
|
data: data,
|
|
meta: meta
|
|
}));
|
|
}
|
|
_context5.next = 25;
|
|
break;
|
|
|
|
case 22:
|
|
_context5.prev = 22;
|
|
_context5.t3 = _context5['catch'](3);
|
|
|
|
this.manualValidationErrors.push(new _Error.ValidationError({
|
|
errorCode: _Error.ErrorCodes.INVALID_ASSET_URI,
|
|
fieldPath: fieldPath,
|
|
message: 'cannot access file at \'' + data + '\'',
|
|
data: data,
|
|
meta: meta
|
|
}));
|
|
|
|
case 25:
|
|
case 'end':
|
|
return _context5.stop();
|
|
}
|
|
}
|
|
}, _callee5, this, [[3, 22]]);
|
|
}));
|
|
|
|
function _validateAssetAsync(_x6) {
|
|
return _ref7.apply(this, arguments);
|
|
}
|
|
|
|
return _validateAssetAsync;
|
|
}()
|
|
}, {
|
|
key: 'validateProperty',
|
|
value: function () {
|
|
var _ref8 = _asyncToGenerator(regeneratorRuntime.mark(function _callee6(fieldPath, data) {
|
|
var subSchema;
|
|
return regeneratorRuntime.wrap(function _callee6$(_context6) {
|
|
while (1) {
|
|
switch (_context6.prev = _context6.next) {
|
|
case 0:
|
|
subSchema = (0, _Util.fieldPathToSchema)(this.schema, fieldPath);
|
|
|
|
this.ajv.validate(subSchema, data);
|
|
|
|
if (!(subSchema.meta && subSchema.meta.asset)) {
|
|
_context6.next = 5;
|
|
break;
|
|
}
|
|
|
|
_context6.next = 5;
|
|
return this._validateAssetAsync({ fieldPath: fieldPath, data: data, meta: subSchema.meta });
|
|
|
|
case 5:
|
|
this._throwOnErrors();
|
|
|
|
case 6:
|
|
case 'end':
|
|
return _context6.stop();
|
|
}
|
|
}
|
|
}, _callee6, this);
|
|
}));
|
|
|
|
function validateProperty(_x7, _x8) {
|
|
return _ref8.apply(this, arguments);
|
|
}
|
|
|
|
return validateProperty;
|
|
}()
|
|
}, {
|
|
key: 'validateName',
|
|
value: function validateName(name) {
|
|
return this.validateProperty('name', name);
|
|
}
|
|
}, {
|
|
key: 'validateSlug',
|
|
value: function validateSlug(slug) {
|
|
return this.validateProperty('slug', slug);
|
|
}
|
|
}, {
|
|
key: 'validateSdkVersion',
|
|
value: function validateSdkVersion(version) {
|
|
return this.validateProperty('sdkVersion', version);
|
|
}
|
|
}, {
|
|
key: 'validateIcon',
|
|
value: function validateIcon(iconPath) {
|
|
return this.validateProperty('icon', iconPath);
|
|
}
|
|
}]);
|
|
|
|
return Schemer;
|
|
}();
|
|
|
|
exports.default = Schemer; |