New features: - Automatic error reporting (enabled by default) - Increase build times by leveraging docker build caches - Fixes: - Fix error handling - Fix vue autodetect - Custom dockerfile is not the default Others: - Cleanup `logs-servers` collection, because old errors are not standardized - New Traefik proxy version - Standardized directory configurations
240 lines
10 KiB
JavaScript
240 lines
10 KiB
JavaScript
/* eslint-disable */
|
|
"use strict";
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.compileResponseValidationSchema = exports.addResponseValidation = exports.convertValidationErrors = exports.validationMessagesFormatters = exports.niceJoin = void 0;
|
|
const ajv_1 = __importDefault(require("ajv"));
|
|
const http_errors_enhanced_1 = require("http-errors-enhanced");
|
|
const interfaces_1 = require("./interfaces");
|
|
const utils_1 = require("./utils");
|
|
function niceJoin(array, lastSeparator = ' and ', separator = ', ') {
|
|
switch (array.length) {
|
|
case 0:
|
|
return '';
|
|
case 1:
|
|
return array[0];
|
|
case 2:
|
|
return array.join(lastSeparator);
|
|
default:
|
|
return array.slice(0, array.length - 1).join(separator) + lastSeparator + array[array.length - 1];
|
|
}
|
|
}
|
|
exports.niceJoin = niceJoin;
|
|
exports.validationMessagesFormatters = {
|
|
contentType: () => 'only JSON payloads are accepted. Please set the "Content-Type" header to start with "application/json"',
|
|
json: () => 'the body payload is not a valid JSON',
|
|
jsonEmpty: () => 'the JSON body payload cannot be empty if the "Content-Type" header is set',
|
|
missing: () => 'must be present',
|
|
unknown: () => 'is not a valid property',
|
|
uuid: () => 'must be a valid GUID (UUID v4)',
|
|
timestamp: () => 'must be a valid ISO 8601 / RFC 3339 timestamp (example: 2018-07-06T12:34:56Z)',
|
|
date: () => 'must be a valid ISO 8601 / RFC 3339 date (example: 2018-07-06)',
|
|
time: () => 'must be a valid ISO 8601 / RFC 3339 time (example: 12:34:56)',
|
|
uri: () => 'must be a valid URI',
|
|
hostname: () => 'must be a valid hostname',
|
|
ipv4: () => 'must be a valid IPv4',
|
|
ipv6: () => 'must be a valid IPv6',
|
|
paramType: (type) => {
|
|
switch (type) {
|
|
case 'integer':
|
|
return 'must be a valid integer number';
|
|
case 'number':
|
|
return 'must be a valid number';
|
|
case 'boolean':
|
|
return 'must be a valid boolean (true or false)';
|
|
case 'object':
|
|
return 'must be a object';
|
|
case 'array':
|
|
return 'must be an array';
|
|
default:
|
|
return 'must be a string';
|
|
}
|
|
},
|
|
presentString: () => 'must be a non empty string',
|
|
minimum: (min) => `must be a number greater than or equal to ${min}`,
|
|
maximum: (max) => `must be a number less than or equal to ${max}`,
|
|
minimumProperties(min) {
|
|
return min === 1 ? 'cannot be a empty object' : `must be a object with at least ${min} properties`;
|
|
},
|
|
maximumProperties(max) {
|
|
return max === 0 ? 'must be a empty object' : `must be a object with at most ${max} properties`;
|
|
},
|
|
minimumItems(min) {
|
|
return min === 1 ? 'cannot be a empty array' : `must be an array with at least ${min} items`;
|
|
},
|
|
maximumItems(max) {
|
|
return max === 0 ? 'must be a empty array' : `must be an array with at most ${max} items`;
|
|
},
|
|
enum: (values) => `must be one of the following values: ${niceJoin(values.map((f) => `"${f}"`), ' or ')}`,
|
|
pattern: (pattern) => `must match pattern "${pattern.replace(/\(\?:/g, '(')}"`,
|
|
invalidResponseCode: (code) => `This endpoint cannot respond with HTTP status ${code}.`,
|
|
invalidResponse: (code) => `The response returned from the endpoint violates its specification for the HTTP status ${code}.`,
|
|
invalidFormat: (format) => `must match format "${format}" (format)`
|
|
};
|
|
function convertValidationErrors(section, data, validationErrors) {
|
|
const errors = {};
|
|
if (section === 'querystring') {
|
|
section = 'query';
|
|
}
|
|
// For each error
|
|
for (const e of validationErrors) {
|
|
let message = '';
|
|
let pattern;
|
|
let value;
|
|
let reason;
|
|
// Normalize the key
|
|
let key = e.dataPath;
|
|
if (key.startsWith('.')) {
|
|
key = key.substring(1);
|
|
}
|
|
// Remove useless quotes
|
|
/* istanbul ignore next */
|
|
if (key.startsWith('[') && key.endsWith(']')) {
|
|
key = key.substring(1, key.length - 1);
|
|
}
|
|
// Depending on the type
|
|
switch (e.keyword) {
|
|
case 'required':
|
|
case 'dependencies':
|
|
key = e.params.missingProperty;
|
|
message = exports.validationMessagesFormatters.missing();
|
|
break;
|
|
case 'additionalProperties':
|
|
key = e.params.additionalProperty;
|
|
message = exports.validationMessagesFormatters.unknown();
|
|
break;
|
|
case 'type':
|
|
message = exports.validationMessagesFormatters.paramType(e.params.type);
|
|
break;
|
|
case 'minProperties':
|
|
message = exports.validationMessagesFormatters.minimumProperties(e.params.limit);
|
|
break;
|
|
case 'maxProperties':
|
|
message = exports.validationMessagesFormatters.maximumProperties(e.params.limit);
|
|
break;
|
|
case 'minItems':
|
|
message = exports.validationMessagesFormatters.minimumItems(e.params.limit);
|
|
break;
|
|
case 'maxItems':
|
|
message = exports.validationMessagesFormatters.maximumItems(e.params.limit);
|
|
break;
|
|
case 'minimum':
|
|
message = exports.validationMessagesFormatters.minimum(e.params.limit);
|
|
break;
|
|
case 'maximum':
|
|
message = exports.validationMessagesFormatters.maximum(e.params.limit);
|
|
break;
|
|
case 'enum':
|
|
message = exports.validationMessagesFormatters.enum(e.params.allowedValues);
|
|
break;
|
|
case 'pattern':
|
|
pattern = e.params.pattern;
|
|
value = utils_1.get(data, key);
|
|
if (pattern === '.+' && !value) {
|
|
message = exports.validationMessagesFormatters.presentString();
|
|
}
|
|
else {
|
|
message = exports.validationMessagesFormatters.pattern(e.params.pattern);
|
|
}
|
|
break;
|
|
case 'format':
|
|
reason = e.params.format;
|
|
// Normalize the key
|
|
if (reason === 'date-time') {
|
|
reason = 'timestamp';
|
|
}
|
|
message = (exports.validationMessagesFormatters[reason] || exports.validationMessagesFormatters.invalidFormat)(reason);
|
|
break;
|
|
}
|
|
// No custom message was found, default to input one replacing the starting verb and adding some path info
|
|
if (!message.length) {
|
|
message = `${e.message.replace(/^should/, 'must')} (${e.keyword})`;
|
|
}
|
|
// Remove useless quotes
|
|
/* istanbul ignore next */
|
|
if (key.match(/(?:^['"])(?:[^.]+)(?:['"]$)/)) {
|
|
key = key.substring(1, key.length - 1);
|
|
}
|
|
// Fix empty properties
|
|
if (!key) {
|
|
key = '$root';
|
|
}
|
|
key = key.replace(/^\//, '');
|
|
errors[key] = message;
|
|
}
|
|
return { [section]: errors };
|
|
}
|
|
exports.convertValidationErrors = convertValidationErrors;
|
|
function addResponseValidation(route) {
|
|
var _a;
|
|
if (!((_a = route.schema) === null || _a === void 0 ? void 0 : _a.response)) {
|
|
return;
|
|
}
|
|
const validators = {};
|
|
/*
|
|
Add these validators to the list of the one to compile once the server is started.
|
|
This makes possible to handle shared schemas.
|
|
*/
|
|
this[interfaces_1.kHttpErrorsEnhancedResponseValidations].push([
|
|
this,
|
|
validators,
|
|
Object.entries(route.schema.response)
|
|
]);
|
|
// Note that this hook is not called for non JSON payloads therefore validation is not possible in such cases
|
|
route.preSerialization = async function (request, reply, payload) {
|
|
const statusCode = reply.raw.statusCode;
|
|
// Never validate error 500
|
|
if (statusCode === http_errors_enhanced_1.INTERNAL_SERVER_ERROR) {
|
|
return payload;
|
|
}
|
|
// No validator, it means the HTTP status is not allowed
|
|
const validator = validators[statusCode];
|
|
if (!validator) {
|
|
if (request[interfaces_1.kHttpErrorsEnhancedConfiguration].allowUndeclaredResponses) {
|
|
return payload;
|
|
}
|
|
throw new http_errors_enhanced_1.InternalServerError(exports.validationMessagesFormatters.invalidResponseCode(statusCode));
|
|
}
|
|
// Now validate the payload
|
|
const valid = validator(payload);
|
|
if (!valid) {
|
|
throw new http_errors_enhanced_1.InternalServerError(exports.validationMessagesFormatters.invalidResponse(statusCode), {
|
|
failedValidations: convertValidationErrors('response', payload, validator.errors)
|
|
});
|
|
}
|
|
return payload;
|
|
};
|
|
}
|
|
exports.addResponseValidation = addResponseValidation;
|
|
function compileResponseValidationSchema(configuration) {
|
|
// Fix CJS/ESM interoperability
|
|
// @ts-expect-error
|
|
let AjvConstructor = ajv_1.default;
|
|
/* istanbul ignore next */
|
|
if (AjvConstructor.default) {
|
|
AjvConstructor = AjvConstructor.default;
|
|
}
|
|
const hasCustomizer = typeof configuration.responseValidatorCustomizer === 'function';
|
|
for (const [instance, validators, schemas] of this[interfaces_1.kHttpErrorsEnhancedResponseValidations]) {
|
|
// @ts-expect-error
|
|
const compiler = new AjvConstructor({
|
|
// The fastify defaults, with the exception of removeAdditional and coerceTypes, which have been reversed
|
|
removeAdditional: false,
|
|
useDefaults: true,
|
|
coerceTypes: false,
|
|
allErrors: true
|
|
});
|
|
compiler.addSchema(Object.values(instance.getSchemas()));
|
|
compiler.addKeyword('example');
|
|
if (hasCustomizer) {
|
|
configuration.responseValidatorCustomizer(compiler);
|
|
}
|
|
for (const [code, schema] of schemas) {
|
|
validators[code] = compiler.compile(schema);
|
|
}
|
|
}
|
|
}
|
|
exports.compileResponseValidationSchema = compileResponseValidationSchema;
|