Skip to content

Commit

Permalink
[HTTP/OAS] zod support (#186190)
Browse files Browse the repository at this point in the history
  • Loading branch information
jloleysens committed Jul 19, 2024
1 parent 001436c commit 6a7a400
Show file tree
Hide file tree
Showing 34 changed files with 1,253 additions and 87 deletions.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,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
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -937,6 +937,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",
"@langchain/community": "^0.2.4",
"@langchain/core": "0.2.3",
Expand Down Expand Up @@ -1762,7 +1763,7 @@
"xmlbuilder": "13.0.2",
"yargs": "^15.4.1",
"yarn-deduplicate": "^6.0.2",
"zod-to-json-schema": "^3.22.3"
"zod-to-json-schema": "^3.23.0"
},
"packageManager": "yarn@1.22.21"
}
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ describe('Router', () => {
(context, req, res) => res.ok({})
)
).toThrowErrorMatchingInlineSnapshot(
`"Expected a valid validation logic declared with '@kbn/config-schema' package or a RouteValidationFunction at key: [params]."`
`"Expected a valid validation logic declared with '@kbn/config-schema' package, '@kbn/zod' package or a RouteValidationFunction at key: [params]."`
);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import type {
VersionedRouter,
RouteRegistrar,
} from '@kbn/core-http-server';
import { isZod } from '@kbn/zod';
import { validBodyOutput, getRequestValidation } from '@kbn/core-http-server';
import { RouteValidator } from './validator';
import { CoreVersionedRouter } from './versioned_router';
Expand Down Expand Up @@ -73,9 +74,9 @@ function routeSchemasFromRouteConfig<P, Q, B>(
if (route.validate !== false) {
const validation = getRequestValidation(route.validate);
Object.entries(validation).forEach(([key, schema]) => {
if (!(isConfigSchema(schema) || typeof schema === 'function')) {
if (!(isConfigSchema(schema) || isZod(schema) || typeof schema === 'function')) {
throw new Error(
`Expected a valid validation logic declared with '@kbn/config-schema' package or a RouteValidationFunction at key: [${key}].`
`Expected a valid validation logic declared with '@kbn/config-schema' package, '@kbn/zod' package or a RouteValidationFunction at key: [${key}].`
);
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ import {
type RouteValidator,
} from '@kbn/core-http-server';
import type { ObjectType, Type } from '@kbn/config-schema';
import type { ZodEsque } from '@kbn/zod';

function isStatusCode(key: string) {
return !isNaN(parseInt(key, 10));
}

interface ResponseValidation {
[statusCode: number]: { body: () => ObjectType | Type<unknown> };
[statusCode: number]: { body: () => ObjectType | Type<unknown> | ZodEsque<unknown> };
}

export function prepareResponseValidation(validation: ResponseValidation): ResponseValidation {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import { schema, Type } from '@kbn/config-schema';
import { z } from '@kbn/zod';
import { RouteValidationError } from '@kbn/core-http-server';
import { RouteValidator } from './validator';

Expand Down Expand Up @@ -80,6 +81,23 @@ describe('Router validator', () => {
);
});

it('should validate and infer the type from a zod-schema ObjectType', () => {
const schemaValidation = RouteValidator.from({
params: z.object({
foo: z.string(),
}),
});

expect(schemaValidation.getParams({ foo: 'bar' })).toStrictEqual({ foo: 'bar' });
expect(schemaValidation.getParams({ foo: 'bar' }).foo.toUpperCase()).toBe('BAR'); // It knows it's a string! :)
expect(() => schemaValidation.getParams({ foo: 1 })).toThrowError(
/Expected string, received number/
);
expect(() => schemaValidation.getParams({})).toThrowError(/Required/);
expect(() => schemaValidation.getParams(undefined)).toThrowError(/Required/);
expect(() => schemaValidation.getParams({}, 'myField')).toThrowError(/Required/);
});

it('should validate and infer the type from a config-schema non-ObjectType', () => {
const schemaValidation = RouteValidator.from({ params: schema.buffer() });

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import { Stream } from 'stream';
import { isZod } from '@kbn/zod';
import { ValidationError, schema, isConfigSchema } from '@kbn/config-schema';
import type {
RouteValidationSpec,
Expand Down Expand Up @@ -119,6 +120,8 @@ export class RouteValidator<P = {}, Q = {}, B = {}> {
): T {
if (isConfigSchema(validationRule)) {
return validationRule.validate(data, {}, namespace);
} else if (isZod(validationRule)) {
return validationRule.parse(data);
} else if (typeof validationRule === 'function') {
return this.validateFunction(validationRule, data, namespace);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"@kbn/std",
"@kbn/utility-types",
"@kbn/config-schema",
"@kbn/zod",
"@kbn/es-errors",
"@kbn/core-http-server",
"@kbn/hapi-mocks",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
* Side Public License, v 1.
*/

import { ObjectType, SchemaTypeError, Type } from '@kbn/config-schema';
import { type ObjectType, SchemaTypeError, type Type } from '@kbn/config-schema';
import type { ZodEsque } from '@kbn/zod';

/**
* Error to return when the validation is not successful.
Expand Down Expand Up @@ -79,7 +80,11 @@ export type RouteValidationFunction<T> = (
*
* @public
*/
export type RouteValidationSpec<T> = ObjectType | Type<T> | RouteValidationFunction<T>;
export type RouteValidationSpec<T> =
| ObjectType
| Type<T>
| ZodEsque<T>
| RouteValidationFunction<T>;

/**
* The configuration object to the RouteValidator class.
Expand Down Expand Up @@ -208,4 +213,4 @@ export type RouteValidator<P, Q, B> =
* @return A @kbn/config-schema schema
* @public
*/
export type LazyValidator = () => Type<unknown>;
export type LazyValidator = () => Type<unknown> | ZodEsque<unknown>;
3 changes: 2 additions & 1 deletion packages/core/http/core-http-server/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"@kbn/config-schema",
"@kbn/utility-types",
"@kbn/core-base-common",
"@kbn/core-http-common"
"@kbn/core-http-common",
"@kbn/zod"
],
"exclude": [
"target/**/*",
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 6a7a400

Please sign in to comment.