import { Request , ResponseObject , ResponseToolkit } from '@hapi/hapi' ; import Boom from '@hapi/boom' ; import { isConfigSchema } from '@osd/config-schema' ; import { Logger } from '../../logging' ; import { LegacyOpenSearchErrorHelpers } from '../../opensearch/legacy/errors' ; import { isUnauthorizedError as isOpenSearchUnauthorizedError , UnauthorizedError as OpenSearchNotAuthorizedError , } from '../../opensearch/client/errors' ; import { OpenSearchDashboardsRequest } from './request' ; import { OpenSearchDashboardsResponseFactory , opensearchDashboardsResponseFactory , IOpenSearchDashboardsResponse , ErrorHttpResponseOptions , } from './response' ; import { RouteConfig , RouteConfigOptions , RouteMethod , validBodyOutput } from './route' ; import { HapiResponseAdapter } from './response_adapter' ; import { RequestHandlerContext } from '../../../server' ; import { wrapErrors } from './error_wrapper' ; import { RouteValidator } from './validator' ; interface RouterRoute { method : RouteMethod ; path : string ; options : RouteConfigOptions < RouteMethod > ; handler : ( req : Request , responseToolkit : ResponseToolkit ) => Promise < ResponseObject | Boom . Boom < any > > ; } public export type RouteRegistrar < Method extends RouteMethod > = < P , Q , B > ( route : RouteConfig < P , Q , B , Method > , handler : RequestHandler < P , Q , B , Method > ) => void ; /**
 * Registers route handlers for specified resource path and method.
 * See  RouteConfig and  RequestHandler for more information about arguments to route registrations.
 *
 *  public export interface IRouter { /**
     * Resulted path
     */ routerPath : string ; param route RouteConfig - a route configuration.
     *  param handler RequestHandler - a function to call to respond to an incoming request
      get : RouteRegistrar < 'get' > ; param route RouteConfig - a route configuration.
     *  param handler RequestHandler - a function to call to respond to an incoming request
      post : RouteRegistrar < 'post' > ; param route RouteConfig - a route configuration.
     *  param handler RequestHandler - a function to call to respond to an incoming request
      put : RouteRegistrar < 'put' > ; param route RouteConfig - a route configuration.
     *  param handler RequestHandler - a function to call to respond to an incoming request
      patch : RouteRegistrar < 'patch' > ; param route RouteConfig - a route configuration.
     *  param handler RequestHandler - a function to call to respond to an incoming request
      delete : RouteRegistrar < 'delete' > ; param handler RequestHandler - a route handler to wrap
      handleLegacyErrors : RequestHandlerWrapper ; returns internal getRoutes : ( ) => RouterRoute [ ] ; } export type ContextEnhancer < P , Q , B , Method extends RouteMethod > = ( handler : RequestHandler < P , Q , B , Method > ) => RequestHandlerEnhanced < P , Q , B , Method > ; function getRouteFullPath ( routerPath , routePath ) { const routePathStartIndex = routerPath . endsWith ( '/' ) && routePath . startsWith ( '/' ) ? 1 : 0 ; return `${ routerPath }${ routePath . slice ( routePathStartIndex ) }` ; } returns function routeSchemasFromRouteConfig < P , Q , B > ( route , routeMethod ) { if ( route . validate === undefined ) { throw new Error ( `The [${ routeMethod }] at [${ route . path }] does not have a 'validate' specified. Use 'false' as the value if you want to bypass validation.` ) ; } if ( route . validate !== false ) { Object . entries ( route . validate ) . forEach ( ( [ key , schema ] ) => { if ( ! ( isConfigSchema ( schema ) || typeof schema === 'function' ) ) { throw new Error ( `Expected a valid validation logic declared with '@osd/config-schema' package or a RouteValidationFunction at key: [${ key }].` ) ; } } ) ; } if ( route . validate ) { return RouteValidator . from ( route . validate ) ; } } param method param routeConfig function validOptions ( method , routeConfig ) { const shouldNotHavePayload = [ 'head' , 'get' ] . includes ( method ) ; const { options = { } , validate } = routeConfig ; const shouldValidateBody = ( validate && ! ! validate . body ) || ! ! options . body ; const { output } = options . body || { } ; if ( typeof output === 'string' && ! validBodyOutput . includes ( output ) ) { throw new Error ( `[options.body.output: '${ output }'] in route ${ method . toUpperCase ( ) } ${ routeConfig . path } is not valid. Only '${ validBodyOutput . join ( "' or '" ) }' are valid.` ) ; } const body = shouldNotHavePayload ? undefined : { output : ! shouldValidateBody ? ( 'stream' as const ) : undefined , parse : ! shouldValidateBody ? false : undefined , ... options . body , } ; return { ... options , body } ; } internal export class Router implements IRouter { public routes : Array < Readonly < RouterRoute > > = [ ] ; public get : IRouter [ 'get' ] ; public post : IRouter [ 'post' ] ; public delete : IRouter [ 'delete' ] ; public put : IRouter [ 'put' ] ; public patch : IRouter [ 'patch' ] ; constructor ( public readonly routerPath , private readonly log , private readonly enhanceWithContext ) { const buildMethod = < Method extends RouteMethod > ( method ) => < P , Q , B > ( route , handler ) => { const routeSchemas = routeSchemasFromRouteConfig ( route , method ) ; this . routes . push ( { handler : async ( req , responseToolkit ) => await this . handle ( { routeSchemas , request : req , responseToolkit , handler : this . enhanceWithContext ( handler ) , } ) , method , path : getRouteFullPath ( this . routerPath , route . path ) , options : validOptions ( method , route ) , } ) ; } ; this . get = buildMethod ( 'get' ) ; this . post = buildMethod ( 'post' ) ; this . delete = buildMethod ( 'delete' ) ; this . put = buildMethod ( 'put' ) ; this . patch = buildMethod ( 'patch' ) ; } public getRoutes ( ) { return [ ... this . routes ] ; } public handleLegacyErrors = wrapErrors ; private async handle < P , Q , B > ( { routeSchemas , request , responseToolkit , handler , } ) { let opensearchDashboardsRequest ; const hapiResponseAdapter = new HapiResponseAdapter ( responseToolkit ) ; try { opensearchDashboardsRequest = OpenSearchDashboardsRequest . from ( request , routeSchemas ) ; } catch ( e ) { return hapiResponseAdapter . toBadRequest ( e . message ) ; } try { const opensearchDashboardsResponse = await handler ( opensearchDashboardsRequest , opensearchDashboardsResponseFactory ) ; return hapiResponseAdapter . handle ( opensearchDashboardsResponse ) ; } catch ( e ) { this . log . error ( e ) ; if ( isOpenSearchUnauthorizedError ( e ) ) { return hapiResponseAdapter . handle ( opensearchDashboardsResponseFactory . unauthorized ( convertOpenSearchUnauthorized ( e ) ) ) ; } if ( LegacyOpenSearchErrorHelpers . isNotAuthorizedError ( e ) ) { return e ; } return hapiResponseAdapter . toInternalError ( ) ; } } } const convertOpenSearchUnauthorized = ( e ) : ErrorHttpResponseOptions => { const getAuthenticateHeaderValue = ( ) => { const header = Object . entries ( e . headers ) . find ( ( [ key ] ) => key . toLowerCase ( ) === 'www-authenticate' ) ; return header ? header [ 1 ] : 'Basic realm="Authorization Required"' ; } ; return { body : e . message , headers : { 'www-authenticate' : getAuthenticateHeaderValue ( ) , } , } ; } ; type WithoutHeadArgument < T > = T extends ( first : any , ... rest : infer Params ) => infer Return ? ( ... rest : Params ) => Return : never ; type RequestHandlerEnhanced < P , Q , B , Method extends RouteMethod > = WithoutHeadArgument < RequestHandler < P , Q , B , Method > > ; /**
 * A function executed when route path matched requested resource path.
 * Request handler is expected to return a result of one of  OpenSearchDashboardsResponseFactory functions.
 *  param context RequestHandlerContext - the core context exposed for this request.
 *  param request OpenSearchDashboardsRequest - object containing information about requested resource,
 * such as path, method, headers, parameters, query, body, etc.
 *  param response OpenSearchDashboardsResponseFactory - a set of helper functions used to respond to a request.
 *
 *  example public export type RequestHandler < P = unknown , Q = unknown , B = unknown , Method extends RouteMethod = any , ResponseFactory extends OpenSearchDashboardsResponseFactory = OpenSearchDashboardsResponseFactory > = ( context : RequestHandlerContext , request : OpenSearchDashboardsRequest < P , Q , B , Method > , response : ResponseFactory ) => IOpenSearchDashboardsResponse < any > | Promise < IOpenSearchDashboardsResponse < any > > ; /**
 * Type-safe wrapper for  RequestHandler function.
 *  example public export type RequestHandlerWrapper = < P , Q , B , Method extends RouteMethod = any , ResponseFactory extends OpenSearchDashboardsResponseFactory = OpenSearchDashboardsResponseFactory > ( handler : RequestHandler < P , Q , B , Method , ResponseFactory > ) => RequestHandler < P , Q , B , Method , ResponseFactory > ;