mirror of https://gitee.com/bigwinds/arangodb
295 lines
8.3 KiB
JavaScript
295 lines
8.3 KiB
JavaScript
'use strict';
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / DISCLAIMER
|
|
// /
|
|
// / Copyright 2016 ArangoDB GmbH, Cologne, Germany
|
|
// /
|
|
// / Licensed under the Apache License, Version 2.0 (the "License")
|
|
// / you may not use this file except in compliance with the License.
|
|
// / You may obtain a copy of the License at
|
|
// /
|
|
// / http://www.apache.org/licenses/LICENSE-2.0
|
|
// /
|
|
// / Unless required by applicable law or agreed to in writing, software
|
|
// / distributed under the License is distributed on an "AS IS" BASIS,
|
|
// / WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// / See the License for the specific language governing permissions and
|
|
// / limitations under the License.
|
|
// /
|
|
// / Copyright holder is ArangoDB GmbH, Cologne, Germany
|
|
// /
|
|
// / @author Alan Plum
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
const _ = require('lodash');
|
|
const dd = require('dedent');
|
|
const il = require('@arangodb/util').inline;
|
|
const joi = require('joi');
|
|
const util = require('util');
|
|
const statuses = require('statuses');
|
|
const mimeTypes = require('mime-types');
|
|
const mediaTyper = require('media-typer');
|
|
const ArangoError = require('@arangodb').ArangoError;
|
|
const ERROR_BAD_PARAMETER = require('@arangodb').errors.ERROR_BAD_PARAMETER;
|
|
|
|
function normalizeMimeType (mime) {
|
|
if (mime === 'binary') {
|
|
mime = 'application/octet-stream';
|
|
}
|
|
const contentType = mimeTypes.contentType(mime) || mime;
|
|
const parsed = mediaTyper.parse(contentType);
|
|
return mediaTyper.format(_.pick(parsed, [
|
|
'type',
|
|
'subtype',
|
|
'suffix'
|
|
]));
|
|
}
|
|
|
|
function findVariations (options) {
|
|
const variants = [[]];
|
|
for (let i = options.length - 1; i >= 0; i--) {
|
|
const isOptional = options[i];
|
|
const len = variants.length;
|
|
if (isOptional) {
|
|
for (let k = 0; k < len; k++) {
|
|
variants.push([...variants[k]]);
|
|
}
|
|
}
|
|
for (let k = 0; k < len; k++) {
|
|
variants[k].unshift(i);
|
|
}
|
|
}
|
|
return variants;
|
|
}
|
|
|
|
function runValidation (methodName, paramName, type, value) {
|
|
const warnings = [];
|
|
if (typeof type === 'string') {
|
|
if (typeof value !== type) {
|
|
return {
|
|
value,
|
|
error: new Error(`${paramName} must be a ${type}, not ${typeof value}`),
|
|
warnings};
|
|
}
|
|
return {value, error: null, warnings};
|
|
} else if (typeof type === 'function') {
|
|
const result = type(value);
|
|
if (result.warnings) {
|
|
for (const warning of result.warnings) {
|
|
warnings.push(warning.replace(/^"value"/, paramName));
|
|
}
|
|
}
|
|
if (result.error) {
|
|
result.error.message = result.error.message
|
|
.replace(/^"value"/, paramName);
|
|
return {value: result.value, error: result.error, warnings};
|
|
}
|
|
return {value: result.value, error: null, warnings};
|
|
} else {
|
|
throw new Error(il`
|
|
Unknown type for parameter ${paramName} in ${methodName}:
|
|
${util.inspect(type)}
|
|
`);
|
|
}
|
|
}
|
|
|
|
module.exports = exports = function (methodName, paramNames, types, values) {
|
|
const optionals = paramNames
|
|
.map((paramName) => paramName.charAt(paramName.length - 1) === '?');
|
|
paramNames = optionals.map((isOptional, i) => (
|
|
isOptional ? paramNames[i].slice(0, -1) : paramNames[i]
|
|
));
|
|
for (let i = values.length - 1; i >= 0; i--) {
|
|
if (values[i] !== undefined) {
|
|
break;
|
|
}
|
|
values.pop();
|
|
}
|
|
const validations = types.map(() => []);
|
|
const warnings = [];
|
|
let output;
|
|
let error = null;
|
|
for (const variation of findVariations(optionals)) {
|
|
if (variation.length < values.length) {
|
|
continue;
|
|
}
|
|
output = []; let err = null;
|
|
for (let i = 0; i < variation.length; i++) {
|
|
const index = variation[i];
|
|
const result = validations[i][index] || runValidation(
|
|
methodName,
|
|
paramNames[index],
|
|
types[index],
|
|
values[i]
|
|
);
|
|
for (const warning of result.warnings) {
|
|
warnings.push(warning);
|
|
}
|
|
if (result.error) {
|
|
err = result.error;
|
|
break;
|
|
}
|
|
output[index] = result.value;
|
|
}
|
|
if (err) {
|
|
error = err;
|
|
} else {
|
|
error = null;
|
|
break;
|
|
}
|
|
}
|
|
if (warnings.length || error) {
|
|
const signature = optionals.map((isOptional, i) => (
|
|
isOptional ? `[${paramNames[i]}]` : paramNames[i]
|
|
)).join(', ');
|
|
for (const warning of warnings) {
|
|
console.warnLines(`${methodName}(${signature}): ${warning}`);
|
|
}
|
|
if (error) {
|
|
throw Object.assign(
|
|
new ArangoError({
|
|
errorNum: ERROR_BAD_PARAMETER.code,
|
|
errorMessage: dd`
|
|
${ERROR_BAD_PARAMETER.message}
|
|
${error.message}
|
|
Method: ${methodName}(${signature})
|
|
Arguments: ${util.inspect(values, {simpleJoi: true, depth: 3})}
|
|
`
|
|
}), {cause: error}
|
|
);
|
|
}
|
|
}
|
|
return output;
|
|
};
|
|
|
|
exports.validateMiddleware = function (value) {
|
|
if (value && (typeof value === 'function' || typeof value.register === 'function')) {
|
|
return {value, error: null};
|
|
}
|
|
return {value, error: new Error('"value" is not a valid middleware')};
|
|
};
|
|
|
|
exports.validateMountable = function (value) {
|
|
if (value && value.isFoxxRouter) {
|
|
return {value, error: null};
|
|
}
|
|
return exports.validateMiddleware(value);
|
|
};
|
|
|
|
exports.validateStatus = function (value) {
|
|
let status = value;
|
|
if (typeof status === 'string') {
|
|
try {
|
|
status = statuses(status);
|
|
} catch (e) {
|
|
return {value, error: Object.assign(
|
|
new Error('"value" must be a valid status name.'),
|
|
{cause: e}
|
|
)};
|
|
}
|
|
}
|
|
status = Number(status);
|
|
if (Number.isNaN(status)) {
|
|
return {value, error: new Error('"value" must be a number')};
|
|
}
|
|
return {value: status, error: null};
|
|
};
|
|
|
|
exports.validateSchema = function (value) {
|
|
if (value) {
|
|
if (value.isJoi || typeof value.validate === 'function') {
|
|
return {value, error: null};
|
|
}
|
|
try {
|
|
return {value: joi.object(value).required(), error: null};
|
|
} catch (e) {
|
|
return {value, error: Object.assign(
|
|
new Error('"value" must be a schema'),
|
|
{cause: e}
|
|
)};
|
|
}
|
|
}
|
|
return {value, error: new Error('"value" must be a schema.')};
|
|
};
|
|
|
|
exports.validateModel = function (value) {
|
|
const warnings = [];
|
|
let model = value;
|
|
let multiple = false;
|
|
if (model === null) {
|
|
return {value: {model, multiple}, error: null};
|
|
}
|
|
if (Array.isArray(model)) {
|
|
if (model.length !== 1) {
|
|
return {value, error: new Error(il`
|
|
"value" must be a model or schema
|
|
or an array containing exactly one model or schema.
|
|
If you are trying to use multiple schemas
|
|
try using joi.alternatives instead.
|
|
`), warnings};
|
|
}
|
|
model = model[0];
|
|
multiple = true;
|
|
}
|
|
|
|
const index = multiple ? '[0]' : '';
|
|
if (!model || typeof model !== 'object') {
|
|
return {value, error: new Error(
|
|
`"value"${index} is not an object`
|
|
)};
|
|
}
|
|
|
|
const result = exports.validateSchema(model);
|
|
if (!result.error) {
|
|
model = {schema: result.value};
|
|
}
|
|
|
|
if (model.schema) {
|
|
const result = exports.validateSchema(model.schema);
|
|
if (result.error) {
|
|
result.error.message = result.error.message
|
|
.replace(/^"value"/, `"value"${index}.schema`);
|
|
return {value, error: result.error};
|
|
}
|
|
model.schema = result.value;
|
|
}
|
|
if (!model.forClient && typeof model.toClient === 'function') {
|
|
warnings.push(il`
|
|
"value"${index} has unexpected "toClient" method.
|
|
Did you mean "forClient"?
|
|
`);
|
|
}
|
|
if (model.forClient && typeof model.forClient !== 'function') {
|
|
return {value, error: new Error(il`
|
|
"value"${index}.forClient must be a function,
|
|
not ${typeof model.forClient}.
|
|
`), warnings};
|
|
}
|
|
if (model.fromClient && typeof model.fromClient !== 'function') {
|
|
return {value, error: new Error(il`
|
|
"value"${index}.fromClient must be a function,
|
|
not ${typeof model.fromClient}.
|
|
`), warnings};
|
|
}
|
|
return {value: {model, multiple}, error: null};
|
|
};
|
|
|
|
exports.validateMimes = function (value) {
|
|
if (!Array.isArray(value)) {
|
|
return {value, error: new Error(`"value" must be an array.`)};
|
|
}
|
|
const mimes = [];
|
|
for (let i = 0; i < value.length; i++) {
|
|
try {
|
|
mimes.push(normalizeMimeType(value[i]));
|
|
} catch (e) {
|
|
return {value, error: Object.assign(
|
|
new Error(`"value"[${i}] must be a valid MIME type.`),
|
|
{cause: e}
|
|
)};
|
|
}
|
|
}
|
|
return {value: mimes, error: null};
|
|
};
|