mirror of https://gitee.com/bigwinds/arangodb
306 lines
8.7 KiB
JavaScript
306 lines
8.7 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 runValidation (methodName, paramName, type, value) {
|
|
const warnings = [];
|
|
if (typeof type === 'string') {
|
|
let isa = typeof value;
|
|
if (Array.isArray(value) && type !== 'object') {
|
|
isa = 'array';
|
|
}
|
|
if (isa !== type) {
|
|
return {
|
|
value,
|
|
error: new Error(`${paramName} must be a ${type}, not ${isa}`),
|
|
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, values, ...variations) {
|
|
for (let i = values.length - 1; i >= 0; i--) {
|
|
if (values[i] !== undefined) {
|
|
break;
|
|
}
|
|
values.pop();
|
|
}
|
|
const warnings = [];
|
|
const validations = new Map();
|
|
let output;
|
|
let error = null;
|
|
for (const variation of variations) {
|
|
if (variation.length > values.length) {
|
|
if (!error) {
|
|
const [name] = variation[values.length];
|
|
error = new Error(`${name} is required`);
|
|
error.default = true;
|
|
error.variation = variation;
|
|
}
|
|
continue;
|
|
}
|
|
output = [];
|
|
let err = null;
|
|
const multis = new Set();
|
|
for (let i = 0; i < variation.length; i++) {
|
|
const [name, type] = variation[i];
|
|
const value = values[i];
|
|
if (!validations.has(value)) {
|
|
validations.set(value, new Map());
|
|
}
|
|
const results = validations.get(value);
|
|
if (!results.has(type)) {
|
|
results.set(type, runValidation(
|
|
methodName,
|
|
name,
|
|
type,
|
|
value
|
|
));
|
|
}
|
|
const result = results.get(type);
|
|
for (const warning of result.warnings) {
|
|
warning.variation = variation;
|
|
warnings.push(warning);
|
|
}
|
|
if (result.error) {
|
|
err = result.error;
|
|
break;
|
|
}
|
|
output[i] = result.value;
|
|
if (multis.has(name)) {
|
|
output[name].push(result.value);
|
|
} else if (hasOwnProperty.call(output, name)) {
|
|
multis.add(name);
|
|
output[name] = [output[name], result.value];
|
|
} else {
|
|
output[name] = result.value;
|
|
}
|
|
}
|
|
if (err) {
|
|
if (error && !error.default) {
|
|
continue;
|
|
}
|
|
error = err;
|
|
error.variation = variation;
|
|
} else {
|
|
error = null;
|
|
break;
|
|
}
|
|
}
|
|
if (warnings.length || error) {
|
|
for (const warning of warnings) {
|
|
const signature = warning.variation.map(([name]) => name).join(', ');
|
|
console.warnLines(`${methodName}(${signature}): ${warning}`);
|
|
}
|
|
if (error) {
|
|
const signature = error.variation.map(([name]) => name).join(', ');
|
|
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};
|
|
};
|