From 08b9fe935acc0f6744b07683d424b9fc53baba85 Mon Sep 17 00:00:00 2001 From: Josh Dover Date: Mon, 27 Jan 2020 15:36:47 -0700 Subject: [PATCH 1/2] Add example for migrating pre-handlers --- src/core/MIGRATION_EXAMPLES.md | 135 +++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) diff --git a/src/core/MIGRATION_EXAMPLES.md b/src/core/MIGRATION_EXAMPLES.md index 568980f50117d..ad56e79f3df73 100644 --- a/src/core/MIGRATION_EXAMPLES.md +++ b/src/core/MIGRATION_EXAMPLES.md @@ -14,6 +14,7 @@ APIs to their New Platform equivalents. - [3. New Platform shim using New Platform router](#3-new-platform-shim-using-new-platform-router) - [4. New Platform plugin](#4-new-platform-plugin) - [Accessing Services](#accessing-services) + - [Migrating Hapi "pre" handlers](#migrating-hapi-pre-handlers) - [Chrome](#chrome) - [Updating an application navlink](#updating-application-navlink) - [Chromeless Applications](#chromeless-applications) @@ -450,6 +451,140 @@ class Plugin { } ``` +### Migrating Hapi "pre" handlers + +In the Legacy Platform, routes could provide a "pre" option in their config to +register a function that should be run prior to the route handler. These +"pre" handlers allow routes to share some business logic that may do some +pre-work or validation. In Kibana, these are often used for license checks. + +The Kibana Platform's HTTP interface does not provide this functionality, +however it is simple enough to port over using a higher-order function that can +wrap the route handler. + +#### Simple example + +In this simple example, a pre-handler is used to either abort the request with +an error or continue as normal. This is a simple "gate-keeping" pattern. + +```ts +// Legacy pre-handler +const licensePreRouting = (request) => { + const licenseInfo = getMyPluginLicenseInfo(request.server.plugins.xpack_main); + if (!licenseInfo.isHighEnough()) { + throw Boom.forbidden(`You don't have the right license for MyPlugin!`); + } +} + +server.route({ + method: 'GET', + path: '/api/my-plugin/do-something', + config: { + pre: [{ method: licensePreRouting }] + }, + handler: (req) => { + return doSomethingInteresting(); + } +}) +``` + +In the Kibana Platform, the same functionality can be acheived by creating a +function that takes a route handler (or factory for a route handler) as an +argument and either invokes it in the successful case or returns an error +response in the failure case. + +We'll call this a "high-order handler" similar to the "high-order component" +pattern common in the React ecosystem. + +```ts +// New Platform high-order handler +const checkLicense = ( + handler: RequestHandler +): RequestHandler => { + return (context, req, res) => { + const licenseInfo = getMyPluginLicenseInfo(context.licensing.license); + + if (licenseInfo.isHighEnough()) { + return handler(context, req, res); + } else { + return res.forbidden({ body: `You don't have the right license for MyPlugin!` }); + } + } +} + +router.get( + { path: '/api/my-plugin/do-something', validate: false }, + checkLicense(async (context, req, res) => { + const results = doSomethingInteresting(); + return res.ok({ body: results }); + }), +) +``` + +#### Full Example + +In some cases, the route handler may need access to data that the pre-handler +retrieves. In this case, you can utilize a handler _factory_ rather than a raw +handler. + +```ts +// Legacy pre-handler +const licensePreRouting = (request) => { + const licenseInfo = getMyPluginLicenseInfo(request.server.plugins.xpack_main); + if (licenseInfo.isHighEnough()) { + // In this case, the return value of the pre-handler is made available on + // whatever the 'assign' option is in the route config. + return licenseInfo; + } else { + // In this case, the route handler is never called and the user gets this + // error message + throw Boom.forbidden(`You don't have the right license for MyPlugin!`); + } +} + +server.route({ + method: 'GET', + path: '/api/my-plugin/do-something', + config: { + pre: [{ method: licensePreRouting, assign: 'licenseInfo' }] + }, + handler: (req) => { + const licenseInfo = req.pre.licenseInfo; + return doSomethingInteresting(licenseInfo); + } +}) +``` + +In this case, we'll use a handler factory as the argument to our high-order +handler. This way the high-order handler can pass arbitrary arguments to the +route handler. + +```ts +// New Platform high-order handler +const checkLicense = ( + handlerFactory: (licenseInfo: MyPluginLicenseInfo) => RequestHandler +): RequestHandler => { + return (context, req, res) => { + const licenseInfo = getMyPluginLicenseInfo(context.licensing.license); + + if (licenseInfo.isHighEnough()) { + const handler = handlerFactory(licenseInfo); + return handler(context, req, res); + } else { + return res.forbidden({ body: `You don't have the right license for MyPlugin!` }); + } + } +} + +router.get( + { path: '/api/my-plugin/do-something', validate: false }, + checkLicense(licenseInfo => async (context, req, res) => { + const results = doSomethingInteresting(licenseInfo); + return res.ok({ body: results }); + }), +) +``` + ## Chrome In the Legacy Platform, the `ui/chrome` import contained APIs for a very wide From ee9110a0191f5c9aa32d1a6a518b327b63776215 Mon Sep 17 00:00:00 2001 From: Josh Dover Date: Thu, 30 Jan 2020 12:56:15 -0700 Subject: [PATCH 2/2] PR comments --- src/core/MIGRATION_EXAMPLES.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/core/MIGRATION_EXAMPLES.md b/src/core/MIGRATION_EXAMPLES.md index ad56e79f3df73..5517dfa7f9a23 100644 --- a/src/core/MIGRATION_EXAMPLES.md +++ b/src/core/MIGRATION_EXAMPLES.md @@ -471,7 +471,7 @@ an error or continue as normal. This is a simple "gate-keeping" pattern. // Legacy pre-handler const licensePreRouting = (request) => { const licenseInfo = getMyPluginLicenseInfo(request.server.plugins.xpack_main); - if (!licenseInfo.isHighEnough()) { + if (!licenseInfo.isOneOf(['gold', 'platinum', 'trial'])) { throw Boom.forbidden(`You don't have the right license for MyPlugin!`); } } @@ -504,7 +504,7 @@ const checkLicense = ( return (context, req, res) => { const licenseInfo = getMyPluginLicenseInfo(context.licensing.license); - if (licenseInfo.isHighEnough()) { + if (licenseInfo.hasAtLeast('gold')) { return handler(context, req, res); } else { return res.forbidden({ body: `You don't have the right license for MyPlugin!` }); @@ -531,7 +531,7 @@ handler. // Legacy pre-handler const licensePreRouting = (request) => { const licenseInfo = getMyPluginLicenseInfo(request.server.plugins.xpack_main); - if (licenseInfo.isHighEnough()) { + if (licenseInfo.isOneOf(['gold', 'platinum', 'trial'])) { // In this case, the return value of the pre-handler is made available on // whatever the 'assign' option is in the route config. return licenseInfo; @@ -555,9 +555,11 @@ server.route({ }) ``` -In this case, we'll use a handler factory as the argument to our high-order -handler. This way the high-order handler can pass arbitrary arguments to the -route handler. +In many cases, it may be simpler to duplicate the function call +to retrieve the data again in the main handler. In this other cases, you can +utilize a handler _factory_ rather than a raw handler as the argument to your +high-order handler. This way the high-order handler can pass arbitrary arguments +to the route handler. ```ts // New Platform high-order handler @@ -567,7 +569,7 @@ const checkLicense = ( return (context, req, res) => { const licenseInfo = getMyPluginLicenseInfo(context.licensing.license); - if (licenseInfo.isHighEnough()) { + if (licenseInfo.hasAtLeast('gold')) { const handler = handlerFactory(licenseInfo); return handler(context, req, res); } else {