'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;