Skip to content

Commit

Permalink
Make APIs internal
Browse files Browse the repository at this point in the history
  • Loading branch information
dgieselaar committed Nov 13, 2024
1 parent eb61e23 commit 5e90008
Show file tree
Hide file tree
Showing 18 changed files with 203 additions and 82 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ import {
} from '@kbn/server-route-repository-utils';
import { httpResponseIntoObservable } from '@kbn/sse-utils-client';
import { from } from 'rxjs';
import { HttpFetchOptions, HttpFetchQuery, HttpResponse } from '@kbn/core-http-browser';
import { HttpFetchQuery, HttpResponse } from '@kbn/core-http-browser';
import { omit } from 'lodash';

export function createRepositoryClient<
TRepository extends ServerRouteRepository,
TClientOptions extends HttpFetchOptions = {}
TClientOptions extends Record<string, any> = {}
>(core: CoreStart | CoreSetup): RouteRepositoryClient<TRepository, TClientOptions> {
const fetch = (
endpoint: string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,5 @@ export function parseEndpoint(endpoint: string) {
throw new Error(`Endpoint ${endpoint} was not prefixed with a valid HTTP method`);
}

if (!version && pathname.startsWith('/api')) {
throw new Error(`Missing version for public endpoint ${endpoint}`);
}

return { method, pathname, version };
}
61 changes: 43 additions & 18 deletions packages/kbn-server-route-repository-utils/src/typings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*/

import type { HttpFetchOptions } from '@kbn/core-http-browser';
import type { IKibanaResponse } from '@kbn/core-http-server';
import type { IKibanaResponse, RouteAccess } from '@kbn/core-http-server';
import type {
KibanaRequest,
KibanaResponseFactory,
Expand Down Expand Up @@ -56,18 +56,36 @@ export interface RouteState {
}

export type ServerRouteHandlerResources = Record<string, any>;
export type ServerRouteCreateOptions = Record<string, any>;

type ValidateEndpoint<TEndpoint extends string> = string extends TEndpoint
export interface ServerRouteCreateOptions {
[x: string]: any;
}

type RouteMethodOf<TEndpoint extends string> = TEndpoint extends `${infer TRouteMethod} ${string}`
? TRouteMethod extends RouteMethod
? TRouteMethod
: RouteMethod
: RouteMethod;

type IsPublicEndpoint<
TEndpoint extends string,
TRouteAccess extends RouteAccess | undefined
> = TRouteAccess extends 'public'
? true
: TEndpoint extends `${string} ${string} ${string}`
: TRouteAccess extends 'internal'
? false
: TEndpoint extends `${string} /api${string}`
? true
: TEndpoint extends `${string} ${infer TPathname}`
? TPathname extends `/internal/${string}`
? true
: false
: false;

type IsVersionSpecified<TEndpoint extends string> =
TEndpoint extends `${string} ${string} ${string}` ? true : false;

type ValidateEndpoint<
TEndpoint extends string,
TRouteAccess extends RouteAccess | undefined
> = IsPublicEndpoint<TEndpoint, TRouteAccess> extends true ? IsVersionSpecified<TEndpoint> : true;

type IsAny<T> = 1 | 0 extends (T extends never ? 1 : 0) ? true : false;

// this ensures only plain objects can be returned, if it's not one
Expand Down Expand Up @@ -131,12 +149,16 @@ export type CreateServerRouteFactory<
> = <
TEndpoint extends string,
TReturnType extends ServerRouteHandlerReturnType,
TRouteParamsRT extends RouteParamsRT | undefined = undefined
TRouteParamsRT extends RouteParamsRT | undefined = undefined,
TRouteAccess extends RouteAccess | undefined = undefined
>(
options: {
endpoint: ValidateEndpoint<TEndpoint> extends true ? TEndpoint : never;
endpoint: ValidateEndpoint<TEndpoint, TRouteAccess> extends true ? TEndpoint : never;
handler: ServerRouteHandler<TRouteHandlerResources, TRouteParamsRT, TReturnType>;
params?: TRouteParamsRT;
options?: RouteConfigOptions<RouteMethodOf<TEndpoint>> & {
access?: TRouteAccess;
};
} & TRouteCreateOptions
) => Record<
TEndpoint,
Expand All @@ -154,16 +176,15 @@ export type ServerRoute<
TRouteParamsRT extends RouteParamsRT | undefined,
TRouteHandlerResources extends ServerRouteHandlerResources,
TReturnType extends ServerRouteHandlerReturnType,
TRouteCreateOptions extends ServerRouteCreateOptions
> = {
TRouteCreateOptions extends ServerRouteCreateOptions | undefined
> = TRouteCreateOptions & {
endpoint: TEndpoint;
handler: ServerRouteHandler<TRouteHandlerResources, TRouteParamsRT, TReturnType>;
} & TRouteCreateOptions &
(TRouteParamsRT extends RouteParamsRT ? { params: TRouteParamsRT } : {});
} & (TRouteParamsRT extends RouteParamsRT ? { params: TRouteParamsRT } : {});

export type ServerRouteRepository = Record<
string,
ServerRoute<string, RouteParamsRT | undefined, any, any, Record<string, any>>
ServerRoute<string, RouteParamsRT | undefined, any, any, ServerRouteCreateOptions | undefined>
>;

type ClientRequestParamsOfType<TRouteParamsRT extends RouteParamsRT> =
Expand Down Expand Up @@ -229,7 +250,7 @@ export type ClientRequestParamsOf<
infer TRouteParamsRT,
any,
any,
ServerRouteCreateOptions
ServerRouteCreateOptions | undefined
>
? TRouteParamsRT extends RouteParamsRT
? ClientRequestParamsOfType<TRouteParamsRT>
Expand All @@ -249,13 +270,17 @@ export interface RouteRepositoryClient<
fetch<TEndpoint extends Extract<keyof TServerRouteRepository, string>>(
endpoint: TEndpoint,
...args: MaybeOptionalArgs<
ClientRequestParamsOf<TServerRouteRepository, TEndpoint> & TAdditionalClientOptions
ClientRequestParamsOf<TServerRouteRepository, TEndpoint> &
TAdditionalClientOptions &
HttpFetchOptions
>
): Promise<ReturnOf<TServerRouteRepository, TEndpoint>>;
stream<TEndpoint extends Extract<keyof TServerRouteRepository, string>>(
endpoint: TEndpoint,
...args: MaybeOptionalArgs<
ClientRequestParamsOf<TServerRouteRepository, TEndpoint> & TAdditionalClientOptions
ClientRequestParamsOf<TServerRouteRepository, TEndpoint> &
TAdditionalClientOptions &
HttpFetchOptions
>
): ReturnOf<TServerRouteRepository, TEndpoint> extends Observable<infer TReturnType>
? TReturnType extends ServerSentEvent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
*/

import type {
DefaultRouteCreateOptions,
DefaultRouteHandlerResources,
ServerRouteCreateOptions,
ServerRouteHandlerResources,
Expand All @@ -17,7 +16,7 @@ import type { CreateServerRouteFactory } from '@kbn/server-route-repository-util

export function createServerRouteFactory<
TRouteHandlerResources extends ServerRouteHandlerResources = DefaultRouteHandlerResources,
TRouteCreateOptions extends ServerRouteCreateOptions = DefaultRouteCreateOptions
TRouteCreateOptions extends ServerRouteCreateOptions = {}
>(): CreateServerRouteFactory<TRouteHandlerResources, TRouteCreateOptions> {
return (route) => ({ [route.endpoint]: route } as any);
}
15 changes: 10 additions & 5 deletions packages/kbn-server-route-repository/src/register_routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
} from '@kbn/server-route-repository-utils';
import { observableIntoEventSourceStream } from '@kbn/sse-utils-server';
import { isZod } from '@kbn/zod';
import { merge } from 'lodash';
import { merge, omit } from 'lodash';
import { Observable, isObservable } from 'rxjs';
import { ServerSentEvent } from '@kbn/sse-utils';
import { passThroughValidationObject, noParamsValidationObject } from './validation_objects';
Expand Down Expand Up @@ -52,7 +52,10 @@ export function registerRoutes<TDependencies extends Record<string, any>>({
const router = core.http.createRouter();

routes.forEach((route) => {
const { params, endpoint, options, handler } = route;
const { endpoint, handler } = route;

const params = 'params' in route ? route.params : undefined;
const options = 'options' in route ? route.options! : {};

const { method, pathname, version } = parseEndpoint(endpoint);

Expand Down Expand Up @@ -141,16 +144,18 @@ export function registerRoutes<TDependencies extends Record<string, any>>({
router[method](
{
path: pathname,
options,
options: omit(options, 'security'),
validate: validationObject,
security: options.security,
},
wrappedHandler
);
} else {
router.versioned[method]({
path: pathname,
access: pathname.startsWith('/internal/') ? 'internal' : 'public',
options,
access: options.access ?? (pathname.startsWith('/internal/') ? 'internal' : 'public'),
options: omit(options, 'access', 'security'),
security: options.security,
}).addVersion(
{
version,
Expand Down
30 changes: 20 additions & 10 deletions packages/kbn-server-route-repository/src/test_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,36 +83,46 @@ createServerRouteFactory<{ context: { getSpaceId: () => string } }, {}>()({
});

// Create options are available when registering a route.
createServerRouteFactory<{}, { options: { tags: string[] } }>()({
createServerRouteFactory<{}, {}>()({
endpoint: 'GET /internal/endpoint_with_params',
params: t.type({
path: t.type({
serviceName: t.string,
}),
}),
options: {
tags: [],
},
handler: async (resources) => {
assertType<{ params: { path: { serviceName: string } } }>(resources);
},
});

// Public APIs should be versioned
createServerRouteFactory<{}, { options: { tags: string[] } }>()({
createServerRouteFactory<{}, { tags: string[] }>()({
// @ts-expect-error
endpoint: 'GET /api/endpoint_with_params',
tags: [],
handler: async (resources) => {},
});

// `access` is respected
createServerRouteFactory<{}, { tags: string[] }>()({
endpoint: 'GET /api/endpoint_with_params',
options: {
tags: [],
access: 'internal',
},
tags: [],
handler: async (resources) => {},
});

createServerRouteFactory<{}, { options: { tags: string[] } }>()({
// specifying additional options makes them required
// @ts-expect-error
createServerRouteFactory<{}, { tags: string[] }>()({
endpoint: 'GET /api/endpoint_with_params 2023-10-31',
options: {
tags: [],
},
handler: async (resources) => {},
});

createServerRouteFactory<{}, { tags: string[] }>()({
endpoint: 'GET /api/endpoint_with_params 2023-10-31',
tags: [],
handler: async (resources) => {},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import type {
CoreSetup,
CustomRequestHandlerContext,
CoreStart,
RouteConfigOptions,
IScopedClusterClient,
IUiSettingsClient,
SavedObjectsClientContract,
Expand Down Expand Up @@ -60,9 +59,8 @@ export interface APMRouteCreateOptions {
| 'oas-tag:APM agent keys'
| 'oas-tag:APM annotations'
>;
body?: { accepts: Array<'application/json' | 'multipart/form-data'> };
disableTelemetry?: boolean;
} & RouteConfigOptions<any>;
};
}

export type TelemetryUsageCounter = ReturnType<UsageCollectionSetup['createUsageCounter']>;
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/streams/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export class Plugin implements StreamsPluginClass {
const createStatusObservable = once((repositoryClient: StreamsRepositoryClient) => {
return from(
repositoryClient
.fetch('GET /internal/streams/_status', {
.fetch('GET /api/streams/_status', {
signal: new AbortController().signal,
})
.then((response) => ({
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/streams/server/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import { esqlRoutes } from './esql/route';
import { deleteStreamRoute } from './streams/delete';
import { disableStreamsRoute } from './streams/disable';
import { editStreamRoute } from './streams/edit';
import { enableStreamsRoute } from './streams/enable';
import { forkStreamsRoute } from './streams/fork';
Expand All @@ -25,6 +26,7 @@ export const StreamsRouteRepository = {
...listStreamsRoute,
...streamsStatusRoutes,
...esqlRoutes,
...disableStreamsRoute,
};

export type StreamsRouteRepository = typeof StreamsRouteRepository;
21 changes: 14 additions & 7 deletions x-pack/plugins/streams/server/routes/streams/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import { z } from '@kbn/zod';
import { IScopedClusterClient } from '@kbn/core-elasticsearch-server';
import { Logger } from '@kbn/logging';
import { badRequest, internal, notFound } from '@hapi/boom';
import {
DefinitionNotFound,
ForkConditionMissing,
Expand All @@ -20,9 +21,9 @@ import { MalformedStreamId } from '../../lib/streams/errors/malformed_stream_id'
import { getParentId } from '../../lib/streams/helpers/hierarchy';

export const deleteStreamRoute = createServerRoute({
endpoint: 'DELETE /api/streams/{id} 2023-10-31',
endpoint: 'DELETE /api/streams/{id}',
options: {
access: 'public',
access: 'internal',
security: {
authz: {
requiredPrivileges: ['streams_write'],
Expand All @@ -34,7 +35,13 @@ export const deleteStreamRoute = createServerRoute({
id: z.string(),
}),
}),
handler: async ({ response, params, logger, request, getScopedClients }) => {
handler: async ({
response,
params,
logger,
request,
getScopedClients,
}): Promise<{ acknowledged: true }> => {
try {
const { scopedClusterClient } = await getScopedClients({ request });

Expand All @@ -47,21 +54,21 @@ export const deleteStreamRoute = createServerRoute({

await deleteStream(scopedClusterClient, params.path.id, logger);

return response.ok({ body: { acknowledged: true } });
return { acknowledged: true };
} catch (e) {
if (e instanceof IndexTemplateNotFound || e instanceof DefinitionNotFound) {
return response.notFound({ body: e });
throw notFound(e);
}

if (
e instanceof SecurityException ||
e instanceof ForkConditionMissing ||
e instanceof MalformedStreamId
) {
return response.customError({ body: e, statusCode: 400 });
throw badRequest(e);
}

return response.customError({ body: e, statusCode: 500 });
throw internal(e);
}
},
});
Expand Down
Loading

0 comments on commit 5e90008

Please sign in to comment.