Skip to content

Commit 103095a

Browse files
authored
[skip-ci] Add example for migrating pre-handlers (#56080) (#56436)
1 parent dec20a8 commit 103095a

File tree

1 file changed

+137
-0
lines changed

1 file changed

+137
-0
lines changed

src/core/MIGRATION_EXAMPLES.md

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ APIs to their New Platform equivalents.
1414
- [3. New Platform shim using New Platform router](#3-new-platform-shim-using-new-platform-router)
1515
- [4. New Platform plugin](#4-new-platform-plugin)
1616
- [Accessing Services](#accessing-services)
17+
- [Migrating Hapi "pre" handlers](#migrating-hapi-pre-handlers)
1718
- [Chrome](#chrome)
1819
- [Updating an application navlink](#updating-application-navlink)
1920
- [Chromeless Applications](#chromeless-applications)
@@ -450,6 +451,142 @@ class Plugin {
450451
}
451452
```
452453
454+
### Migrating Hapi "pre" handlers
455+
456+
In the Legacy Platform, routes could provide a "pre" option in their config to
457+
register a function that should be run prior to the route handler. These
458+
"pre" handlers allow routes to share some business logic that may do some
459+
pre-work or validation. In Kibana, these are often used for license checks.
460+
461+
The Kibana Platform's HTTP interface does not provide this functionality,
462+
however it is simple enough to port over using a higher-order function that can
463+
wrap the route handler.
464+
465+
#### Simple example
466+
467+
In this simple example, a pre-handler is used to either abort the request with
468+
an error or continue as normal. This is a simple "gate-keeping" pattern.
469+
470+
```ts
471+
// Legacy pre-handler
472+
const licensePreRouting = (request) => {
473+
const licenseInfo = getMyPluginLicenseInfo(request.server.plugins.xpack_main);
474+
if (!licenseInfo.isOneOf(['gold', 'platinum', 'trial'])) {
475+
throw Boom.forbidden(`You don't have the right license for MyPlugin!`);
476+
}
477+
}
478+
479+
server.route({
480+
method: 'GET',
481+
path: '/api/my-plugin/do-something',
482+
config: {
483+
pre: [{ method: licensePreRouting }]
484+
},
485+
handler: (req) => {
486+
return doSomethingInteresting();
487+
}
488+
})
489+
```
490+
491+
In the Kibana Platform, the same functionality can be acheived by creating a
492+
function that takes a route handler (or factory for a route handler) as an
493+
argument and either invokes it in the successful case or returns an error
494+
response in the failure case.
495+
496+
We'll call this a "high-order handler" similar to the "high-order component"
497+
pattern common in the React ecosystem.
498+
499+
```ts
500+
// New Platform high-order handler
501+
const checkLicense = <P, Q, B>(
502+
handler: RequestHandler<P, Q, B, RouteMethod>
503+
): RequestHandler<P, Q, B, RouteMethod> => {
504+
return (context, req, res) => {
505+
const licenseInfo = getMyPluginLicenseInfo(context.licensing.license);
506+
507+
if (licenseInfo.hasAtLeast('gold')) {
508+
return handler(context, req, res);
509+
} else {
510+
return res.forbidden({ body: `You don't have the right license for MyPlugin!` });
511+
}
512+
}
513+
}
514+
515+
router.get(
516+
{ path: '/api/my-plugin/do-something', validate: false },
517+
checkLicense(async (context, req, res) => {
518+
const results = doSomethingInteresting();
519+
return res.ok({ body: results });
520+
}),
521+
)
522+
```
523+
524+
#### Full Example
525+
526+
In some cases, the route handler may need access to data that the pre-handler
527+
retrieves. In this case, you can utilize a handler _factory_ rather than a raw
528+
handler.
529+
530+
```ts
531+
// Legacy pre-handler
532+
const licensePreRouting = (request) => {
533+
const licenseInfo = getMyPluginLicenseInfo(request.server.plugins.xpack_main);
534+
if (licenseInfo.isOneOf(['gold', 'platinum', 'trial'])) {
535+
// In this case, the return value of the pre-handler is made available on
536+
// whatever the 'assign' option is in the route config.
537+
return licenseInfo;
538+
} else {
539+
// In this case, the route handler is never called and the user gets this
540+
// error message
541+
throw Boom.forbidden(`You don't have the right license for MyPlugin!`);
542+
}
543+
}
544+
545+
server.route({
546+
method: 'GET',
547+
path: '/api/my-plugin/do-something',
548+
config: {
549+
pre: [{ method: licensePreRouting, assign: 'licenseInfo' }]
550+
},
551+
handler: (req) => {
552+
const licenseInfo = req.pre.licenseInfo;
553+
return doSomethingInteresting(licenseInfo);
554+
}
555+
})
556+
```
557+
558+
In many cases, it may be simpler to duplicate the function call
559+
to retrieve the data again in the main handler. In this other cases, you can
560+
utilize a handler _factory_ rather than a raw handler as the argument to your
561+
high-order handler. This way the high-order handler can pass arbitrary arguments
562+
to the route handler.
563+
564+
```ts
565+
// New Platform high-order handler
566+
const checkLicense = <P, Q, B>(
567+
handlerFactory: (licenseInfo: MyPluginLicenseInfo) => RequestHandler<P, Q, B, RouteMethod>
568+
): RequestHandler<P, Q, B, RouteMethod> => {
569+
return (context, req, res) => {
570+
const licenseInfo = getMyPluginLicenseInfo(context.licensing.license);
571+
572+
if (licenseInfo.hasAtLeast('gold')) {
573+
const handler = handlerFactory(licenseInfo);
574+
return handler(context, req, res);
575+
} else {
576+
return res.forbidden({ body: `You don't have the right license for MyPlugin!` });
577+
}
578+
}
579+
}
580+
581+
router.get(
582+
{ path: '/api/my-plugin/do-something', validate: false },
583+
checkLicense(licenseInfo => async (context, req, res) => {
584+
const results = doSomethingInteresting(licenseInfo);
585+
return res.ok({ body: results });
586+
}),
587+
)
588+
```
589+
453590
## Chrome
454591
455592
In the Legacy Platform, the `ui/chrome` import contained APIs for a very wide

0 commit comments

Comments
 (0)