Skip to content

Commit

Permalink
Adds 'discontinued' to OAS meta (#192331)
Browse files Browse the repository at this point in the history
## Summary

In this PR, we add support for a discontinued field to our router
conversion logic at the operation level. `discontinued` indicates the
version or date when the deprecation will be removed, as a string.

Closes #192292

---------

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
Co-authored-by: Jean-Louis Leysens <jloleysens@gmail.com>
  • Loading branch information
3 people authored Sep 13, 2024
1 parent e1ae00d commit 303d8f2
Show file tree
Hide file tree
Showing 24 changed files with 111 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ describe('Router', () => {
validate: { body: validation, query: validation, params: validation },
options: {
deprecated: true,
discontinued: 'post test discontinued',
summary: 'post test summary',
description: 'post test description',
},
Expand All @@ -66,6 +67,7 @@ describe('Router', () => {
isVersioned: false,
options: {
deprecated: true,
discontinued: 'post test discontinued',
summary: 'post test summary',
description: 'post test description',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@ describe('Versioned router', () => {

it('provides the expected metadata', () => {
const versionedRouter = CoreVersionedRouter.from({ router });
versionedRouter.get({ path: '/test/{id}', access: 'internal', deprecated: true });
versionedRouter.get({
path: '/test/{id}',
access: 'internal',
deprecated: true,
discontinued: 'x.y.z',
});
versionedRouter.post({
path: '/test',
access: 'internal',
Expand All @@ -49,6 +54,7 @@ describe('Versioned router', () => {
"options": Object {
"access": "internal",
"deprecated": true,
"discontinued": "x.y.z",
},
"path": "/test/{id}",
},
Expand Down
9 changes: 9 additions & 0 deletions packages/core/http/core-http-server/src/router/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,15 @@ export interface RouteConfigOptions<Method extends RouteMethod> {
* @remarks This will be surfaced in OAS documentation.
*/
deprecated?: boolean;

/**
* Release version or date that this route will be removed
* Use with `deprecated: true`
*
* @remarks This will be surfaced in OAS documentation.
* @example 9.0.0
*/
discontinued?: string;
}

/**
Expand Down
13 changes: 12 additions & 1 deletion packages/core/http/core-http-server/src/versioning/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ export type VersionedRouteConfig<Method extends RouteMethod> = Omit<
RouteConfig<unknown, unknown, unknown, Method>,
'validate' | 'options'
> & {
options?: Omit<RouteConfigOptions<Method>, 'access' | 'description' | 'deprecated'>;
options?: Omit<
RouteConfigOptions<Method>,
'access' | 'description' | 'deprecated' | 'discontinued'
>;
/** See {@link RouteConfigOptions<RouteMethod>['access']} */
access: Exclude<RouteConfigOptions<Method>['access'], undefined>;
/**
Expand Down Expand Up @@ -91,6 +94,14 @@ export type VersionedRouteConfig<Method extends RouteMethod> = Omit<
* @default false
*/
deprecated?: boolean;

/**
* Release version or date that this route will be removed
* Use with `deprecated: true`
*
* @default undefined
*/
discontinued?: string;
};

/**
Expand Down
2 changes: 2 additions & 0 deletions packages/kbn-config-schema/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,7 @@ export const schema = {
export type Schema = typeof schema;

import {
META_FIELD_X_OAS_DISCONTINUED,
META_FIELD_X_OAS_ANY,
META_FIELD_X_OAS_OPTIONAL,
META_FIELD_X_OAS_DEPRECATED,
Expand All @@ -444,6 +445,7 @@ import {
} from './src/oas_meta_fields';

export const metaFields = Object.freeze({
META_FIELD_X_OAS_DISCONTINUED,
META_FIELD_X_OAS_ANY,
META_FIELD_X_OAS_OPTIONAL,
META_FIELD_X_OAS_DEPRECATED,
Expand Down
1 change: 1 addition & 0 deletions packages/kbn-config-schema/src/oas_meta_fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ export const META_FIELD_X_OAS_GET_ADDITIONAL_PROPERTIES =
'x-oas-get-additional-properties' as const;
export const META_FIELD_X_OAS_DEPRECATED = 'x-oas-deprecated' as const;
export const META_FIELD_X_OAS_ANY = 'x-oas-any-type' as const;
export const META_FIELD_X_OAS_DISCONTINUED = 'x-oas-discontinued' as const;
15 changes: 14 additions & 1 deletion packages/kbn-config-schema/src/types/type.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import { get } from 'lodash';
import { internals } from '../internals';
import { Type, TypeOptions } from './type';
import { META_FIELD_X_OAS_DEPRECATED } from '../oas_meta_fields';
import { META_FIELD_X_OAS_DEPRECATED, META_FIELD_X_OAS_DISCONTINUED } from '../oas_meta_fields';

class MyType extends Type<any> {
constructor(opts: TypeOptions<any> = {}) {
Expand All @@ -26,6 +26,19 @@ describe('meta', () => {
const meta = type.getSchema().describe();
expect(get(meta, 'flags.description')).toBe('my description');
expect(get(meta, `metas[0].${META_FIELD_X_OAS_DEPRECATED}`)).toBe(true);
expect(get(meta, `metas[1].${META_FIELD_X_OAS_DISCONTINUED}`)).toBeUndefined();
});

it('sets meta with all fields provided', () => {
const type = new MyType({
meta: { description: 'my description', deprecated: true, 'x-discontinued': '9.0.0' },
});
const meta = type.getSchema().describe();
expect(get(meta, 'flags.description')).toBe('my description');

expect(get(meta, `metas[0].${META_FIELD_X_OAS_DEPRECATED}`)).toBe(true);

expect(get(meta, `metas[1].${META_FIELD_X_OAS_DISCONTINUED}`)).toBe('9.0.0');
});

it('does not set meta when no provided', () => {
Expand Down
9 changes: 8 additions & 1 deletion packages/kbn-config-schema/src/types/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
type WhenOptions,
CustomHelpers,
} from 'joi';
import { META_FIELD_X_OAS_DEPRECATED } from '../oas_meta_fields';
import { META_FIELD_X_OAS_DEPRECATED, META_FIELD_X_OAS_DISCONTINUED } from '../oas_meta_fields';
import { SchemaTypeError, ValidationError } from '../errors';
import { Reference } from '../references';

Expand All @@ -33,6 +33,11 @@ export interface TypeMeta {
* Whether this field is deprecated.
*/
deprecated?: boolean;
/**
* Release version or date that this route will be removed
* @example 9.0.0
*/
'x-discontinued'?: string;
}

export interface TypeOptions<T> {
Expand Down Expand Up @@ -129,6 +134,8 @@ export abstract class Type<V> {
if (options.meta.deprecated) {
schema = schema.meta({ [META_FIELD_X_OAS_DEPRECATED]: true });
}
if (options.meta.deprecated && options.meta['x-discontinued'])
schema = schema.meta({ [META_FIELD_X_OAS_DISCONTINUED]: options.meta['x-discontinued'] });
}

// Attach generic error handler only if it hasn't been attached yet since
Expand Down
20 changes: 20 additions & 0 deletions packages/kbn-router-to-openapispec/openapi-types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

// eslint-disable-next-line import/no-extraneous-dependencies
export * from 'openapi-types';

declare module 'openapi-types' {
export namespace OpenAPIV3 {
export interface BaseSchemaObject {
// Custom OpenAPI field added by Kibana for a new field at the shema level.
'x-discontinued'?: string;
}
}
}

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

Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export const sharedOas = {
'/bar': {
get: {
deprecated: true,
'x-discontinued': 'route discontinued version or date',
operationId: '%2Fbar#0',
parameters: [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export const getVersionedRouterDefaults = (bodySchema?: RuntimeSchema) => ({
summary: 'versioned route',
access: 'public',
deprecated: true,
discontinued: 'route discontinued version or date',
options: {
tags: ['ignore-me', 'oas-tag:versioned'],
},
Expand All @@ -79,7 +80,13 @@ export const getVersionedRouterDefaults = (bodySchema?: RuntimeSchema) => ({
schema.object({
foo: schema.string(),
deprecatedFoo: schema.maybe(
schema.string({ meta: { description: 'deprecated foo', deprecated: true } })
schema.string({
meta: {
description: 'deprecated foo',
deprecated: true,
'x-discontinued': 'route discontinued version or date',
},
})
),
}),
},
Expand Down
2 changes: 1 addition & 1 deletion packages/kbn-router-to-openapispec/src/generate_oas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import type { OpenAPIV3 } from 'openapi-types';
import type { CoreVersionedRouter, Router } from '@kbn/core-http-router-server-internal';
import type { OpenAPIV3 } from 'openapi-types';
import { OasConverter } from './oas_converter';
import { createOperationIdCounter } from './operation_id_counter';
import { processRouter } from './process_router';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@

import Joi from 'joi';
import joiToJsonParse from 'joi-to-json';
import type { OpenAPIV3 } from 'openapi-types';
import { omit } from 'lodash';
import type { OpenAPIV3 } from 'openapi-types';
import { createCtx, postProcessMutations } from './post_process_mutations';
import type { IContext } from './post_process_mutations';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Joi from 'joi';
import { metaFields } from '@kbn/config-schema';
import type { OpenAPIV3 } from 'openapi-types';
import { parse } from '../../parse';
import { deleteField, stripBadDefault, processDeprecated } from './utils';
import { deleteField, stripBadDefault, processDeprecated, processDiscontinued } from './utils';
import { IContext } from '../context';

const {
Expand Down Expand Up @@ -58,13 +58,14 @@ export const processMap = (ctx: IContext, schema: OpenAPIV3.SchemaObject): void

export const processAllTypes = (schema: OpenAPIV3.SchemaObject): void => {
processDeprecated(schema);
processDiscontinued(schema);
stripBadDefault(schema);
};

export const processAnyType = (schema: OpenAPIV3.SchemaObject): void => {
// Map schema to an empty object: `{}`
for (const key of Object.keys(schema)) {
deleteField(schema as Record<any, unknown>, key);
deleteField(schema as unknown as Record<any, unknown>, key);
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import type { OpenAPIV3 } from 'openapi-types';
import { metaFields } from '@kbn/config-schema';
import type { OpenAPIV3 } from 'openapi-types';
import { deleteField, stripBadDefault } from './utils';

const { META_FIELD_X_OAS_OPTIONAL } = metaFields;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import type { OpenAPIV3 } from 'openapi-types';
import { metaFields } from '@kbn/config-schema';
import type { OpenAPIV3 } from 'openapi-types';

export const stripBadDefault = (schema: OpenAPIV3.SchemaObject): void => {
if (schema.default?.special === 'deep') {
Expand All @@ -35,6 +35,13 @@ export const processDeprecated = (schema: OpenAPIV3.SchemaObject): void => {
}
};

export const processDiscontinued = (schema: OpenAPIV3.SchemaObject): void => {
if (metaFields.META_FIELD_X_OAS_DISCONTINUED in schema) {
schema['x-discontinued'] = schema[metaFields.META_FIELD_X_OAS_DISCONTINUED] as string;
deleteField(schema, metaFields.META_FIELD_X_OAS_DISCONTINUED);
}
};

/** Just for type convenience */
export const deleteField = (schema: Record<any, unknown>, field: string): void => {
delete schema[field];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@
*/

import { z, isZod } from '@kbn/zod';
import type { OpenAPIV3 } from 'openapi-types';
// eslint-disable-next-line import/no-extraneous-dependencies
import zodToJsonSchema from 'zod-to-json-schema';
import type { OpenAPIV3 } from 'openapi-types';

import { KnownParameters } from '../../type';
import { validatePathParameters } from '../common';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ describe('processRouter', () => {
getRoutes: () => [
{
path: '/foo',
options: {},
options: { access: 'internal', deprecated: true, discontinued: 'discontinued router' },
handler: jest.fn(),
validationSchemas: { request: { body: schema.object({}) } },
},
Expand Down
1 change: 1 addition & 0 deletions packages/kbn-router-to-openapispec/src/process_router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export const processRouter = (
tags: route.options.tags ? extractTags(route.options.tags) : [],
...(route.options.description ? { description: route.options.description } : {}),
...(route.options.deprecated ? { deprecated: route.options.deprecated } : {}),
...(route.options.discontinued ? { 'x-discontinued': route.options.discontinued } : {}),
requestBody: !!validationSchemas?.body
? {
content: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ const createTestRoute: () => VersionedRouterRoute = () => ({
method: 'get',
options: {
access: 'public',
deprecated: true,
discontinued: 'discontinued versioned router',
options: { body: { access: ['application/test+json'] } as any },
},
handlers: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export const processVersionedRouter = (
tags: route.options.options?.tags ? extractTags(route.options.options.tags) : [],
...(route.options.description ? { description: route.options.description } : {}),
...(route.options.deprecated ? { deprecated: route.options.deprecated } : {}),
...(route.options.discontinued ? { 'x-discontinued': route.options.discontinued } : {}),
requestBody: hasBody
? {
content: hasVersionFilter
Expand Down
4 changes: 2 additions & 2 deletions packages/kbn-router-to-openapispec/src/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import type { OpenAPIV3 } from 'openapi-types';

import type { OpenAPIV3 } from '../openapi-types';
export type { OpenAPIV3 } from '../openapi-types';
export interface KnownParameters {
[paramName: string]: { optional: boolean };
}
Expand Down
1 change: 1 addition & 0 deletions src/dev/precommit_hook/casing_check_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export const IGNORE_FILE_GLOBS = [
'test/package/Vagrantfile',
'x-pack/plugins/security_solution/scripts/endpoint/common/vagrant/Vagrantfile',
'**/test/**/fixtures/**/*',
'packages/kbn-router-to-openapispec/openapi-types.d.ts',

// Required to match the name in the docs.elastic.dev repo.
'dev_docs/nav-kibana-dev.docnav.json',
Expand Down

0 comments on commit 303d8f2

Please sign in to comment.