Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RAM] Rule type params OAS verification and POC #179212

Draft
wants to merge 51 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
02ef114
PoC: Generate OAS spec from versioned router declarations (#155518)
jloleysens May 2, 2023
9e4b520
`@kbn/schema` part of OAS PoC (#156344)
jloleysens May 2, 2023
89f1320
[OAS PoC] Incorporate feedback round I (#156367)
jloleysens May 2, 2023
d0ca55d
[OAS PoC] Restructure code (#156395)
jloleysens May 2, 2023
86905e6
[OAS PoC] Just use `zod` directly (#156447)
jloleysens May 2, 2023
d4f22b2
[CI] Auto-commit changed files from 'node scripts/lint_ts_projects --…
kibanamachine May 2, 2023
d4ec086
[CI] Auto-commit changed files from 'node scripts/generate codeowners'
kibanamachine May 2, 2023
5f035a2
Slight refactor of zod exports and added a test for union special for…
jloleysens May 3, 2023
661dd85
updated the oas extraction algo to take latest params and query for now
jloleysens May 4, 2023
f405fd9
updated contributors note
jloleysens May 4, 2023
630343b
Merge branch 'main' into versioned-router-and-oas
jloleysens May 5, 2023
4cab295
fix types
jloleysens May 5, 2023
22c1ca4
Merge branch 'main' into versioned-router-and-oas
jloleysens May 8, 2023
8d1ec9b
actually pass through the body, params and query types
jloleysens May 8, 2023
aec8677
use ZodEsque to extract _output, rather than _input
jloleysens May 9, 2023
4a2cc6c
use ZodEsque to extract _output, rather than _input
jloleysens May 9, 2023
8c40322
[CI] Auto-commit changed files from 'node scripts/lint_ts_projects --…
kibanamachine May 9, 2023
62d4534
make generic optional, fix type narrowing
jloleysens May 9, 2023
aaff092
added mock
jloleysens May 9, 2023
edc4c0e
Merge branch 'main' into versioned-router-and-oas
jloleysens May 9, 2023
5235e86
remove zod from dev deps
jloleysens May 10, 2023
e582640
Merge branch 'main' into versioned-router-and-oas
jloleysens Aug 24, 2023
2f0ea25
[OAS PoC]OAS endpoint and lazily pass schema (#164710)
jloleysens Aug 25, 2023
ea5bf93
zod-to-json-schema a project dep
jloleysens Aug 25, 2023
9e8d18f
Revert "lazily pass schemas from alerting"
jloleysens Aug 25, 2023
14a9f1d
[CI] Auto-commit changed files from 'node scripts/lint_ts_projects --…
kibanamachine Aug 25, 2023
912d763
Merge branch 'main' into versioned-router-and-oas
jloleysens Jan 3, 2024
c8899b7
reformatting of package, tsconfig and lock files
jloleysens Jan 3, 2024
1a291eb
refactor to support other runtime validation libs
jloleysens Jan 3, 2024
ed56ee8
minor cleanup
jloleysens Jan 3, 2024
d7a505e
fix type
jloleysens Jan 3, 2024
3aa1c81
support joi AND zod schemas
jloleysens Jan 4, 2024
3007b45
add support for descriptions
jloleysens Jan 4, 2024
d52eff6
Merge branch 'main' into versioned-router-and-oas
jloleysens Jan 4, 2024
a8bfeeb
support non-versioned router
jloleysens Jan 5, 2024
a453f97
added support for path filtering and adding response schemas to `Rout…
jloleysens Jan 8, 2024
1de1e12
fixed OAS shape for `Router` output
jloleysens Jan 8, 2024
3356e47
remove noise from resulting schema
jloleysens Jan 8, 2024
6a086c7
also traverse schema array containers
jloleysens Jan 9, 2024
03574f2
populate required field and some other fixes
jloleysens Jan 11, 2024
b57f994
fix return
jloleysens Jan 11, 2024
281d946
fix determining optional path params logic
jloleysens Jan 11, 2024
f5151f6
Merge branch 'main' into versioned-router-and-oas
jloleysens Jan 25, 2024
411c8e6
[CI] Auto-commit changed files from 'node scripts/lint_ts_projects --…
kibanamachine Jan 25, 2024
87d38f7
Merge branch 'main' into versioned-router-and-oas
jloleysens Jan 26, 2024
369df9b
Merge branch 'main' into versioned-router-and-oas
jloleysens Jan 30, 2024
af4b0a0
[CI] Auto-commit changed files from 'node scripts/generate codeowners'
kibanamachine Jan 30, 2024
32ba707
Merge branch 'main' into versioned-router-and-oas
jloleysens Feb 7, 2024
0a6260a
Merge branch 'main' into versioned-router-and-oas-clone
JiaweiWu Mar 12, 2024
d021d35
Merge branch 'main' into issue-178014-oas-verification
JiaweiWu Mar 14, 2024
ab6a25b
Draft and WIP
JiaweiWu Mar 22, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,7 @@ x-pack/examples/gen_ai_streaming_response_example @elastic/response-ops
packages/kbn-generate @elastic/kibana-operations
packages/kbn-generate-console-definitions @elastic/platform-deployment-management
packages/kbn-generate-csv @elastic/appex-sharedux
packages/kbn-generate-oas @elastic/kibana-core
packages/kbn-get-repo-files @elastic/kibana-operations
x-pack/plugins/global_search_bar @elastic/appex-sharedux
x-pack/plugins/global_search @elastic/appex-sharedux
Expand Down Expand Up @@ -889,6 +890,7 @@ packages/kbn-web-worker-stub @elastic/kibana-operations
packages/kbn-whereis-pkg-cli @elastic/kibana-operations
packages/kbn-xstate-utils @elastic/obs-ux-logs-team
packages/kbn-yarn-lock-validator @elastic/kibana-operations
packages/kbn-zod @elastic/kibana-core
packages/kbn-zod-helpers @elastic/security-detection-rule-management
####
## Everything below this line overrides the default assignments for each package.
Expand Down
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,7 @@
"@kbn/gen-ai-streaming-response-example-plugin": "link:x-pack/examples/gen_ai_streaming_response_example",
"@kbn/generate-console-definitions": "link:packages/kbn-generate-console-definitions",
"@kbn/generate-csv": "link:packages/kbn-generate-csv",
"@kbn/generate-oas": "link:packages/kbn-generate-oas",
"@kbn/global-search-bar-plugin": "link:x-pack/plugins/global_search_bar",
"@kbn/global-search-plugin": "link:x-pack/plugins/global_search",
"@kbn/global-search-providers-plugin": "link:x-pack/plugins/global_search_providers",
Expand Down Expand Up @@ -874,6 +875,7 @@
"@kbn/visualizations-plugin": "link:src/plugins/visualizations",
"@kbn/watcher-plugin": "link:x-pack/plugins/watcher",
"@kbn/xstate-utils": "link:packages/kbn-xstate-utils",
"@kbn/zod": "link:packages/kbn-zod",
"@kbn/zod-helpers": "link:packages/kbn-zod-helpers",
"@loaders.gl/core": "^3.4.7",
"@loaders.gl/json": "^3.4.7",
Expand Down Expand Up @@ -999,6 +1001,7 @@
"ipaddr.js": "2.0.0",
"isbinaryfile": "4.0.2",
"joi": "^17.7.1",
"joi-to-json": "^4.2.0",
"jquery": "^3.5.0",
"js-levenshtein": "^1.1.6",
"js-search": "^1.4.3",
Expand Down Expand Up @@ -1147,7 +1150,8 @@
"xterm": "^5.1.0",
"yauzl": "^2.10.0",
"yazl": "^2.5.1",
"zod": "^3.22.3"
"zod": "^3.22.3",
"zod-to-json-schema": "^3.22.3"
},
"devDependencies": {
"@apidevtools/swagger-parser": "^10.0.3",
Expand Down
2 changes: 2 additions & 0 deletions packages/core/http/core-http-router-server-internal/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
*/

export { filterHeaders } from './src/headers';
export { versionHandlerResolvers } from './src/versioned_router';
export { CoreVersionedRouter } from './src/versioned_router';
export { Router, type RouterOptions } from './src/router';
export type { HandlerResolutionStrategy } from './src/versioned_router';
export { isKibanaRequest, isRealRequest, ensureRawRequest, CoreKibanaRequest } from './src/request';
Expand Down
41 changes: 33 additions & 8 deletions packages/core/http/core-http-router-server-internal/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import type {
IRouter,
RequestHandler,
VersionedRouter,
RouteRegistrar,
} from '@kbn/core-http-server';
import { validBodyOutput } from '@kbn/core-http-server';
import { RouteValidator } from './validator';
Expand All @@ -32,6 +33,7 @@ import { CoreKibanaRequest } from './request';
import { kibanaResponseFactory } from './response';
import { HapiResponseAdapter } from './response_adapter';
import { wrapErrors } from './error_wrapper';
import { Method } from './versioned_router/types';

export type ContextEnhancer<
P,
Expand Down Expand Up @@ -133,18 +135,35 @@ export interface RouterOptions {
};
}

/** @internal */
interface InternalRegistrarOptions {
isVersioned: boolean;
}

/** @internal */
type InternalRegistrar<M extends Method, C extends RequestHandlerContextBase> = <P, Q, B>(
route: RouteConfig<P, Q, B, M>,
handler: RequestHandler<P, Q, B, C, M>,
internalOpts?: InternalRegistrarOptions
) => ReturnType<RouteRegistrar<M, C>>;

/** @internal */
interface InternalRouterRoute extends RouterRoute {
readonly isVersioned: boolean;
}

/**
* @internal
*/
export class Router<Context extends RequestHandlerContextBase = RequestHandlerContextBase>
implements IRouter<Context>
{
public routes: Array<Readonly<RouterRoute>> = [];
public get: IRouter<Context>['get'];
public post: IRouter<Context>['post'];
public delete: IRouter<Context>['delete'];
public put: IRouter<Context>['put'];
public patch: IRouter<Context>['patch'];
public routes: Array<Readonly<InternalRouterRoute>> = [];
public get: InternalRegistrar<'get', Context>;
public post: InternalRegistrar<'post', Context>;
public delete: InternalRegistrar<'delete', Context>;
public put: InternalRegistrar<'put', Context>;
public patch: InternalRegistrar<'patch', Context>;

constructor(
public readonly routerPath: string,
Expand All @@ -156,7 +175,8 @@ export class Router<Context extends RequestHandlerContextBase = RequestHandlerCo
<Method extends RouteMethod>(method: Method) =>
<P, Q, B>(
route: RouteConfig<P, Q, B, Method>,
handler: RequestHandler<P, Q, B, Context, Method>
handler: RequestHandler<P, Q, B, Context, Method>,
internalOptions: { isVersioned: boolean } = { isVersioned: false }
) => {
const routeSchemas = routeSchemasFromRouteConfig(route, method);

Expand All @@ -171,6 +191,8 @@ export class Router<Context extends RequestHandlerContextBase = RequestHandlerCo
method,
path: getRouteFullPath(this.routerPath, route.path),
options: validOptions(method, route),
validationSchemas: route.validate, // Added here for introspection
isVersioned: internalOptions.isVersioned,
});
};

Expand All @@ -181,7 +203,10 @@ export class Router<Context extends RequestHandlerContextBase = RequestHandlerCo
this.patch = buildMethod('patch');
}

public getRoutes() {
public getRoutes(excludeVersionedRoutes = false) {
if (excludeVersionedRoutes) {
return this.routes.filter((route) => !route.isVersioned);
}
return [...this.routes];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import type {
RouteConfigOptions,
} from '@kbn/core-http-server';
import type { Mutable } from 'utility-types';
import type { Method } from './types';
import type { Method, VersionedRouterRoute } from './types';
import type { CoreVersionedRouter } from './core_versioned_router';

import { validate } from './validate';
Expand All @@ -47,6 +47,12 @@ export const passThroughValidation = {
query: schema.nullable(schema.any()),
};

function extractValidationSchemaFromHandler(handler: VersionedRouterRoute['handlers'][0]) {
if (handler.options.validate === false) return undefined;
if (typeof handler.options.validate === 'function') return handler.options.validate();
return handler.options.validate;
}

export class CoreVersionedRoute implements VersionedRoute {
private readonly handlers = new Map<
ApiVersion,
Expand Down Expand Up @@ -88,7 +94,8 @@ export class CoreVersionedRoute implements VersionedRoute {
validate: passThroughValidation,
options: this.getRouteConfigOptions(),
},
this.requestHandler
this.requestHandler,
{ isVersioned: true }
);
}

Expand Down Expand Up @@ -156,7 +163,7 @@ export class CoreVersionedRoute implements VersionedRoute {
}]. Available versions are: ${this.versionsToString()}`,
});
}
const validation = handler.options.validate || undefined;
const validation = extractValidationSchemaFromHandler(handler);
if (
validation?.request &&
Boolean(validation.request.body || validation.request.params || validation.request.query)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
* Side Public License, v 1.
*/

import type { IRouter } from '@kbn/core-http-server';
import type { VersionedRouter, VersionedRoute, VersionedRouteConfig } from '@kbn/core-http-server';
import type { Router } from '../router';
import { CoreVersionedRoute } from './core_versioned_route';
import type { HandlerResolutionStrategy, Method, VersionedRouterRoute } from './types';

/** @internal */
export interface VersionedRouterArgs {
router: IRouter;
router: Router;
/**
* Which route resolution algo to use.
* @note default to "oldest", but when running in dev default to "none"
Expand Down Expand Up @@ -56,7 +56,7 @@ export class CoreVersionedRouter implements VersionedRouter {
);
}
private constructor(
public readonly router: IRouter,
public readonly router: Router,
public readonly defaultHandlerResolutionStrategy: HandlerResolutionStrategy = 'oldest',
public readonly isDev: boolean = false,
useVersionResolutionStrategyForInternalPaths: string[] = []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

export { resolvers as versionHandlerResolvers } from './handler_resolvers';
export { CoreVersionedRouter } from './core_versioned_router';
export type { HandlerResolutionStrategy } from './types';
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,50 @@
* Side Public License, v 1.
*/

import type { RouteValidatorFullConfig } from '@kbn/core-http-server';
import {
type VersionedRouteRequestValidation,
type VersionedSpecValidation,
type RouteValidationFunction,
RouteValidationError,
} from '@kbn/core-http-server';
import { z, extractErrorMessage } from '@kbn/zod';
import type { ApiVersion } from '@kbn/core-http-server';
import { instanceofZodType } from '@kbn/zod';
import type { Type } from '@kbn/config-schema';
import { RouteValidator } from '../validator';

function makeValidationFunction(schema: z.ZodTypeAny): RouteValidationFunction<unknown> {
return (data: unknown) => {
const result = schema.safeParse(data);
if (!result.success) {
return {
error: new RouteValidationError(extractErrorMessage(result.error)),
value: undefined,
};
}
return { error: undefined, value: result.data };
};
}

function getValidator(
handler?: VersionedSpecValidation
): RouteValidationFunction<unknown> | Type<unknown> | undefined {
return instanceofZodType(handler)
? makeValidationFunction(handler)
: (handler as RouteValidationFunction<unknown> | Type<unknown> | undefined);
}

/** Will throw if any of the validation checks fail */
export function validate(
data: { body?: unknown; params?: unknown; query?: unknown },
runtimeSchema: RouteValidatorFullConfig<unknown, unknown, unknown>,
runtimeSchema: VersionedRouteRequestValidation<unknown, unknown, unknown>,
version: ApiVersion
): { body: unknown; params: unknown; query: unknown } {
const validator = RouteValidator.from(runtimeSchema);
const validator = RouteValidator.from({
body: getValidator(runtimeSchema.body),
params: getValidator(runtimeSchema.params),
query: getValidator(runtimeSchema.query),
});
return {
params: validator.getParams(data.params, 'request params'),
query: validator.getQuery(data.query, 'request query'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"node"
]
},
"include": [ "**/*.ts" ],
"include": [ "**/*.ts", "src/versioned_router/oas_poc/run.js", "src/versioned_router/oas_poc/generate.js" ],
"kbn_references": [
"@kbn/std",
"@kbn/utility-types",
Expand All @@ -17,7 +17,8 @@
"@kbn/hapi-mocks",
"@kbn/core-logging-server-mocks",
"@kbn/logging",
"@kbn/core-http-common"
"@kbn/core-http-common",
"@kbn/zod"
],
"exclude": [
"target/**/*",
Expand Down
54 changes: 53 additions & 1 deletion packages/core/http/core-http-server-internal/src/http_server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import { Server, Request } from '@hapi/hapi';
import HapiStaticFiles from '@hapi/inert';
import { generateOpenApiDocument } from '@kbn/generate-oas';
import url from 'url';
import { v4 as uuidv4 } from 'uuid';
import {
Expand All @@ -26,7 +27,7 @@ import apm from 'elastic-apm-node';
import Brok from 'brok';
import type { Logger, LoggerFactory } from '@kbn/logging';
import type { InternalExecutionContextSetup } from '@kbn/core-execution-context-server-internal';
import { isSafeMethod } from '@kbn/core-http-router-server-internal';
import { CoreVersionedRouter, isSafeMethod, Router } from '@kbn/core-http-router-server-internal';
import type {
IRouter,
RouteConfigOptions,
Expand Down Expand Up @@ -204,6 +205,10 @@ export class HttpServer {
return this.server !== undefined && this.server.listener.listening;
}

public getRegisteredRouters() {
return [...this.registeredRouters];
}

private registerRouter(router: IRouter) {
if (this.isListening()) {
throw new Error('Routers can be registered only when HTTP server is stopped.');
Expand Down Expand Up @@ -330,6 +335,8 @@ export class HttpServer {
}
}

this.registerOasEndpoint();

await this.server.start();
const serverPath =
this.config && this.config.rewriteBasePath && this.config.basePath !== undefined
Expand Down Expand Up @@ -623,6 +630,51 @@ export class HttpServer {
});
}

private oasCache: undefined | object;
private registerOasEndpoint() {
this.server!.route({
path: '/api/oas',
method: 'GET',
handler: (req, h) => {
// TODO cache the result of generating OAS
// if (this.oasCache) return h.response(this.oasCache);

const pathStartsWith = req.query?.pathStartsWith;
try {
const routers = this.getRegisteredRouters().flatMap((r) => {
const rs: Array<Router | CoreVersionedRouter> = [];
if ((r as Router).getRoutes(true).length > 0) {
rs.push(r as Router);
}
const versionedRouter = r.versioned as CoreVersionedRouter;
if (versionedRouter.getRoutes().length > 0) {
rs.push(versionedRouter);
}
return rs;
});
this.oasCache = generateOpenApiDocument(routers, {
baseUrl: 'todo',
title: 'todo',
version: '0.0.0',
pathStartsWith,
});
return h.response(this.oasCache);
} catch (e) {
this.log.error(e);
return h.response({ message: e.message }).code(500);
}
},
options: {
app: { access: 'public' },
auth: false,
cache: {
privacy: 'public',
otherwise: 'must-revalidate',
},
},
});
}

private registerStaticDir(path: string, dirPath: string) {
if (this.server === undefined) {
throw new Error('Http server is not setup up yet');
Expand Down
Loading