strong-tie/inbound-calls
0
1'use strict'2 3const semver = require('semver')4const assert = require('node:assert')5const kRegisteredPlugins = Symbol.for('registered-plugin')6const {7 kTestInternals8} = require('./symbols.js')9const { exist, existReply, existRequest } = require('./decorate')10const {11 FST_ERR_PLUGIN_VERSION_MISMATCH,12 FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE,13 FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER14} = require('./errors')15 16function getMeta (fn) {17 return fn[Symbol.for('plugin-meta')]18}19 20function getPluginName (func) {21 const display = getDisplayName(func)22 if (display) {23 return display24 }25 26 // let's see if this is a file, and in that case use that27 // this is common for plugins28 const cache = require.cache29 // cache is undefined inside SEA30 if (cache) {31 const keys = Object.keys(cache)32 33 for (let i = 0; i < keys.length; i++) {34 const key = keys[i]35 if (cache[key].exports === func) {36 return key37 }38 }39 }40 41 // if not maybe it's a named function, so use that42 if (func.name) {43 return func.name44 }45 46 return null47}48 49function getFuncPreview (func) {50 // takes the first two lines of the function if nothing else works51 return func.toString().split('\n', 2).map(s => s.trim()).join(' -- ')52}53 54function getDisplayName (fn) {55 return fn[Symbol.for('fastify.display-name')]56}57 58function shouldSkipOverride (fn) {59 return !!fn[Symbol.for('skip-override')]60}61 62function checkDependencies (fn) {63 const meta = getMeta(fn)64 if (!meta) return65 66 const dependencies = meta.dependencies67 if (!dependencies) return68 assert(Array.isArray(dependencies), 'The dependencies should be an array of strings')69 70 dependencies.forEach(dependency => {71 assert(72 this[kRegisteredPlugins].indexOf(dependency) > -1,73 `The dependency '${dependency}' of plugin '${meta.name}' is not registered`74 )75 })76}77 78function checkDecorators (fn) {79 const meta = getMeta(fn)80 if (!meta) return81 82 const { decorators, name } = meta83 if (!decorators) return84 85 if (decorators.fastify) _checkDecorators(this, 'Fastify', decorators.fastify, name)86 if (decorators.reply) _checkDecorators(this, 'Reply', decorators.reply, name)87 if (decorators.request) _checkDecorators(this, 'Request', decorators.request, name)88}89 90const checks = {91 Fastify: exist,92 Request: existRequest,93 Reply: existReply94}95 96function _checkDecorators (that, instance, decorators, name) {97 assert(Array.isArray(decorators), 'The decorators should be an array of strings')98 99 decorators.forEach(decorator => {100 const withPluginName = typeof name === 'string' ? ` required by '${name}'` : ''101 if (!checks[instance].call(that, decorator)) {102 throw new FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE(decorator, withPluginName, instance)103 }104 })105}106 107function checkVersion (fn) {108 const meta = getMeta(fn)109 if (meta == null || meta?.fastify == null) return110 111 const requiredVersion = meta.fastify112 113 const fastifyRc = /-(?:rc|pre|alpha).+$/.test(this.version)114 if (fastifyRc === true && semver.gt(this.version, semver.coerce(requiredVersion)) === true) {115 // A Fastify release candidate phase is taking place. In order to reduce116 // the effort needed to test plugins with the RC, we allow plugins targeting117 // the prior Fastify release to be loaded.118 return119 }120 if (requiredVersion && semver.satisfies(this.version, requiredVersion, { includePrerelease: fastifyRc }) === false) {121 // We are not in a release candidate phase. Thus, we must honor the semver122 // ranges defined by the plugin's metadata. Which is to say, if the plugin123 // expects an older version of Fastify than the _current_ version, we will124 // throw an error.125 throw new FST_ERR_PLUGIN_VERSION_MISMATCH(meta.name, requiredVersion, this.version)126 }127}128 129function registerPluginName (fn) {130 const meta = getMeta(fn)131 if (!meta) return132 133 const name = meta.name134 if (!name) return135 this[kRegisteredPlugins].push(name)136 return name137}138 139function checkPluginHealthiness (fn, pluginName) {140 if (fn.constructor.name === 'AsyncFunction' && fn.length === 3) {141 throw new FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER(pluginName)142 }143}144 145function registerPlugin (fn) {146 const pluginName = registerPluginName.call(this, fn) || getPluginName(fn)147 checkPluginHealthiness.call(this, fn, pluginName)148 checkVersion.call(this, fn)149 checkDecorators.call(this, fn)150 checkDependencies.call(this, fn)151 return shouldSkipOverride(fn)152}153 154module.exports = {155 getPluginName,156 getFuncPreview,157 kRegisteredPlugins,158 getDisplayName,159 registerPlugin160}161 162module.exports[kTestInternals] = {163 shouldSkipOverride,164 getMeta,165 checkDecorators,166 checkDependencies167}168 