Spaces:
Runtime error
Runtime error
| const semver = require('semver') | |
| const assert = require('node:assert') | |
| const kRegisteredPlugins = Symbol.for('registered-plugin') | |
| const { | |
| kTestInternals | |
| } = require('./symbols.js') | |
| const { exist, existReply, existRequest } = require('./decorate') | |
| const { | |
| FST_ERR_PLUGIN_VERSION_MISMATCH, | |
| FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE, | |
| FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER | |
| } = require('./errors') | |
| function getMeta (fn) { | |
| return fn[Symbol.for('plugin-meta')] | |
| } | |
| function getPluginName (func) { | |
| const display = getDisplayName(func) | |
| if (display) { | |
| return display | |
| } | |
| // let's see if this is a file, and in that case use that | |
| // this is common for plugins | |
| const cache = require.cache | |
| // cache is undefined inside SEA | |
| if (cache) { | |
| const keys = Object.keys(cache) | |
| for (let i = 0; i < keys.length; i++) { | |
| const key = keys[i] | |
| if (cache[key].exports === func) { | |
| return key | |
| } | |
| } | |
| } | |
| // if not maybe it's a named function, so use that | |
| if (func.name) { | |
| return func.name | |
| } | |
| return null | |
| } | |
| function getFuncPreview (func) { | |
| // takes the first two lines of the function if nothing else works | |
| return func.toString().split('\n', 2).map(s => s.trim()).join(' -- ') | |
| } | |
| function getDisplayName (fn) { | |
| return fn[Symbol.for('fastify.display-name')] | |
| } | |
| function shouldSkipOverride (fn) { | |
| return !!fn[Symbol.for('skip-override')] | |
| } | |
| function checkDependencies (fn) { | |
| const meta = getMeta(fn) | |
| if (!meta) return | |
| const dependencies = meta.dependencies | |
| if (!dependencies) return | |
| assert(Array.isArray(dependencies), 'The dependencies should be an array of strings') | |
| dependencies.forEach(dependency => { | |
| assert( | |
| this[kRegisteredPlugins].indexOf(dependency) > -1, | |
| `The dependency '${dependency}' of plugin '${meta.name}' is not registered` | |
| ) | |
| }) | |
| } | |
| function checkDecorators (fn) { | |
| const meta = getMeta(fn) | |
| if (!meta) return | |
| const { decorators, name } = meta | |
| if (!decorators) return | |
| if (decorators.fastify) _checkDecorators(this, 'Fastify', decorators.fastify, name) | |
| if (decorators.reply) _checkDecorators(this, 'Reply', decorators.reply, name) | |
| if (decorators.request) _checkDecorators(this, 'Request', decorators.request, name) | |
| } | |
| const checks = { | |
| Fastify: exist, | |
| Request: existRequest, | |
| Reply: existReply | |
| } | |
| function _checkDecorators (that, instance, decorators, name) { | |
| assert(Array.isArray(decorators), 'The decorators should be an array of strings') | |
| decorators.forEach(decorator => { | |
| const withPluginName = typeof name === 'string' ? ` required by '${name}'` : '' | |
| if (!checks[instance].call(that, decorator)) { | |
| throw new FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE(decorator, withPluginName, instance) | |
| } | |
| }) | |
| } | |
| function checkVersion (fn) { | |
| const meta = getMeta(fn) | |
| if (meta == null || meta?.fastify == null) return | |
| const requiredVersion = meta.fastify | |
| const fastifyRc = /-(?:rc|pre|alpha).+$/.test(this.version) | |
| if (fastifyRc === true && semver.gt(this.version, semver.coerce(requiredVersion)) === true) { | |
| // A Fastify release candidate phase is taking place. In order to reduce | |
| // the effort needed to test plugins with the RC, we allow plugins targeting | |
| // the prior Fastify release to be loaded. | |
| return | |
| } | |
| if (requiredVersion && semver.satisfies(this.version, requiredVersion, { includePrerelease: fastifyRc }) === false) { | |
| // We are not in a release candidate phase. Thus, we must honor the semver | |
| // ranges defined by the plugin's metadata. Which is to say, if the plugin | |
| // expects an older version of Fastify than the _current_ version, we will | |
| // throw an error. | |
| throw new FST_ERR_PLUGIN_VERSION_MISMATCH(meta.name, requiredVersion, this.version) | |
| } | |
| } | |
| function registerPluginName (fn) { | |
| const meta = getMeta(fn) | |
| if (!meta) return | |
| const name = meta.name | |
| if (!name) return | |
| this[kRegisteredPlugins].push(name) | |
| return name | |
| } | |
| function checkPluginHealthiness (fn, pluginName) { | |
| if (fn.constructor.name === 'AsyncFunction' && fn.length === 3) { | |
| throw new FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER(pluginName) | |
| } | |
| } | |
| function registerPlugin (fn) { | |
| const pluginName = registerPluginName.call(this, fn) || getPluginName(fn) | |
| checkPluginHealthiness.call(this, fn, pluginName) | |
| checkVersion.call(this, fn) | |
| checkDecorators.call(this, fn) | |
| checkDependencies.call(this, fn) | |
| return shouldSkipOverride(fn) | |
| } | |
| module.exports = { | |
| getPluginName, | |
| getFuncPreview, | |
| kRegisteredPlugins, | |
| getDisplayName, | |
| registerPlugin | |
| } | |
| module.exports[kTestInternals] = { | |
| shouldSkipOverride, | |
| getMeta, | |
| checkDecorators, | |
| checkDependencies | |
| } | |