mirror of
https://github.com/Unleash/unleash.git
synced 2024-12-22 19:07:54 +01:00
refactor: add OpenAPI schema to environments controller (#1682)
* refactor: normalize controller file names * refactor: throw invalid responses in dev mode * refactor: add OpenAPI schema to environments controller * refactor: improve JSON schema prop removal code * refactor: fix empty response specs
This commit is contained in:
parent
18e63d5ea3
commit
adface17c7
52
src/lib/openapi/index.test.ts
Normal file
52
src/lib/openapi/index.test.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import {
|
||||
createOpenApiSchema,
|
||||
createRequestSchema,
|
||||
createResponseSchema,
|
||||
removeJsonSchemaProps,
|
||||
} from './index';
|
||||
|
||||
test('createRequestSchema', () => {
|
||||
expect(createRequestSchema('schemaName')).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"content": Object {
|
||||
"application/json": Object {
|
||||
"schema": Object {
|
||||
"$ref": "#/components/schemas/schemaName",
|
||||
},
|
||||
},
|
||||
},
|
||||
"description": "schemaName",
|
||||
"required": true,
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('createResponseSchema', () => {
|
||||
expect(createResponseSchema('schemaName')).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"content": Object {
|
||||
"application/json": Object {
|
||||
"schema": Object {
|
||||
"$ref": "#/components/schemas/schemaName",
|
||||
},
|
||||
},
|
||||
},
|
||||
"description": "schemaName",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('removeJsonSchemaProps', () => {
|
||||
expect(removeJsonSchemaProps({ a: 'b', $id: 'c', components: {} }))
|
||||
.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"a": "b",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('createOpenApiSchema url', () => {
|
||||
expect(createOpenApiSchema('https://example.com').servers[0].url).toEqual(
|
||||
'https://example.com',
|
||||
);
|
||||
});
|
@ -3,7 +3,6 @@ import { cloneFeatureSchema } from './spec/clone-feature-schema';
|
||||
import { constraintSchema } from './spec/constraint-schema';
|
||||
import { createFeatureSchema } from './spec/create-feature-schema';
|
||||
import { createStrategySchema } from './spec/create-strategy-schema';
|
||||
import { emptySchema } from './spec/empty-schema';
|
||||
import { environmentSchema } from './spec/environment-schema';
|
||||
import { featureEnvironmentSchema } from './spec/feature-environment-schema';
|
||||
import { featureSchema } from './spec/feature-schema';
|
||||
@ -32,6 +31,8 @@ import { updateStrategySchema } from './spec/update-strategy-schema';
|
||||
import { variantSchema } from './spec/variant-schema';
|
||||
import { variantsSchema } from './spec/variants-schema';
|
||||
import { versionSchema } from './spec/version-schema';
|
||||
import { environmentsSchema } from './spec/environments-schema';
|
||||
import { sortOrderSchema } from './spec/sort-order-schema';
|
||||
|
||||
// Schemas must have $id property on the form "#/components/schemas/mySchema".
|
||||
export type SchemaId = typeof schemas[keyof typeof schemas]['$id'];
|
||||
@ -39,6 +40,12 @@ export type SchemaId = typeof schemas[keyof typeof schemas]['$id'];
|
||||
// Schemas must list all $ref schemas in "components", including nested schemas.
|
||||
export type SchemaRef = typeof schemas[keyof typeof schemas]['components'];
|
||||
|
||||
// JSON schema properties that should not be included in the OpenAPI spec.
|
||||
export interface JsonSchemaProps {
|
||||
$id: string;
|
||||
components: object;
|
||||
}
|
||||
|
||||
export interface AdminApiOperation
|
||||
extends Omit<OpenAPIV3.OperationObject, 'tags'> {
|
||||
operationId: string;
|
||||
@ -56,8 +63,8 @@ export const schemas = {
|
||||
constraintSchema,
|
||||
createFeatureSchema,
|
||||
createStrategySchema,
|
||||
emptySchema,
|
||||
environmentSchema,
|
||||
environmentsSchema,
|
||||
featureEnvironmentSchema,
|
||||
featureSchema,
|
||||
featureStrategySchema,
|
||||
@ -74,6 +81,7 @@ export const schemas = {
|
||||
projectEnvironmentSchema,
|
||||
projectSchema,
|
||||
projectsSchema,
|
||||
sortOrderSchema,
|
||||
strategySchema,
|
||||
tagSchema,
|
||||
tagsSchema,
|
||||
@ -116,6 +124,13 @@ export const createResponseSchema = (
|
||||
};
|
||||
};
|
||||
|
||||
// Remove JSONSchema keys that would result in an invalid OpenAPI spec.
|
||||
export const removeJsonSchemaProps = <T extends JsonSchemaProps>(
|
||||
schema: T,
|
||||
): OpenAPIV3.SchemaObject => {
|
||||
return omitKeys(schema, '$id', 'components');
|
||||
};
|
||||
|
||||
export const createOpenApiSchema = (
|
||||
serverUrl?: string,
|
||||
): Omit<OpenAPIV3.Document, 'paths'> => {
|
||||
@ -135,9 +150,7 @@ export const createOpenApiSchema = (
|
||||
name: 'Authorization',
|
||||
},
|
||||
},
|
||||
schemas: mapValues(schemas, (schema) =>
|
||||
omitKeys(schema, '$id', 'components'),
|
||||
),
|
||||
schemas: mapValues(schemas, removeJsonSchemaProps),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@ -0,0 +1,21 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`sortOrderSchema invalid value type 1`] = `
|
||||
Object {
|
||||
"data": Object {
|
||||
"a": "1",
|
||||
},
|
||||
"errors": Array [
|
||||
Object {
|
||||
"instancePath": "/a",
|
||||
"keyword": "type",
|
||||
"message": "must be number",
|
||||
"params": Object {
|
||||
"type": "number",
|
||||
},
|
||||
"schemaPath": "#/additionalProperties/type",
|
||||
},
|
||||
],
|
||||
"schema": "#/components/schemas/sortOrderSchema",
|
||||
}
|
||||
`;
|
3
src/lib/openapi/spec/empty-response.ts
Normal file
3
src/lib/openapi/spec/empty-response.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export const emptyResponse = {
|
||||
description: 'emptyResponse',
|
||||
};
|
@ -1,9 +0,0 @@
|
||||
import { FromSchema } from 'json-schema-to-ts';
|
||||
|
||||
export const emptySchema = {
|
||||
$id: '#/components/schemas/emptySchema',
|
||||
description: 'emptySchema',
|
||||
components: {},
|
||||
} as const;
|
||||
|
||||
export type EmptySchema = FromSchema<typeof emptySchema>;
|
@ -15,6 +15,9 @@ export const environmentSchema = {
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
},
|
||||
protected: {
|
||||
type: 'boolean',
|
||||
},
|
||||
sortOrder: {
|
||||
type: 'number',
|
||||
},
|
||||
|
27
src/lib/openapi/spec/environments-schema.ts
Normal file
27
src/lib/openapi/spec/environments-schema.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { FromSchema } from 'json-schema-to-ts';
|
||||
import { environmentSchema } from './environment-schema';
|
||||
|
||||
export const environmentsSchema = {
|
||||
$id: '#/components/schemas/environmentsSchema',
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['version', 'environments'],
|
||||
properties: {
|
||||
version: {
|
||||
type: 'integer',
|
||||
},
|
||||
environments: {
|
||||
type: 'array',
|
||||
items: {
|
||||
$ref: '#/components/schemas/environmentSchema',
|
||||
},
|
||||
},
|
||||
},
|
||||
components: {
|
||||
schemas: {
|
||||
environmentSchema,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export type EnvironmentsSchema = FromSchema<typeof environmentsSchema>;
|
19
src/lib/openapi/spec/sort-order-schema.test.ts
Normal file
19
src/lib/openapi/spec/sort-order-schema.test.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { validateSchema } from '../validate';
|
||||
import { SortOrderSchema } from './sort-order-schema';
|
||||
|
||||
test('sortOrderSchema', () => {
|
||||
const data: SortOrderSchema = {
|
||||
a: 1,
|
||||
b: 2,
|
||||
};
|
||||
|
||||
expect(
|
||||
validateSchema('#/components/schemas/sortOrderSchema', data),
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
test('sortOrderSchema invalid value type', () => {
|
||||
expect(
|
||||
validateSchema('#/components/schemas/sortOrderSchema', { a: '1' }),
|
||||
).toMatchSnapshot();
|
||||
});
|
12
src/lib/openapi/spec/sort-order-schema.ts
Normal file
12
src/lib/openapi/spec/sort-order-schema.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { FromSchema } from 'json-schema-to-ts';
|
||||
|
||||
export const sortOrderSchema = {
|
||||
$id: '#/components/schemas/sortOrderSchema',
|
||||
type: 'object',
|
||||
additionalProperties: {
|
||||
type: 'number',
|
||||
},
|
||||
components: {},
|
||||
} as const;
|
||||
|
||||
export type SortOrderSchema = FromSchema<typeof sortOrderSchema>;
|
@ -14,7 +14,7 @@ import {
|
||||
import { serializeDates } from '../../types/serialize-dates';
|
||||
import { OpenApiService } from '../../services/openapi-service';
|
||||
import { createResponseSchema } from '../../openapi';
|
||||
import { EmptySchema } from '../../openapi/spec/empty-schema';
|
||||
import { emptyResponse } from '../../openapi/spec/empty-response';
|
||||
|
||||
export default class ArchiveController extends Controller {
|
||||
private readonly logger: Logger;
|
||||
@ -75,7 +75,7 @@ export default class ArchiveController extends Controller {
|
||||
openApiService.validPath({
|
||||
tags: ['admin'],
|
||||
operationId: 'deleteFeature',
|
||||
responses: { 200: createResponseSchema('emptySchema') },
|
||||
responses: { 200: emptyResponse },
|
||||
}),
|
||||
],
|
||||
});
|
||||
@ -90,7 +90,7 @@ export default class ArchiveController extends Controller {
|
||||
openApiService.validPath({
|
||||
tags: ['admin'],
|
||||
operationId: 'reviveFeature',
|
||||
responses: { 200: createResponseSchema('emptySchema') },
|
||||
responses: { 200: emptyResponse },
|
||||
}),
|
||||
],
|
||||
});
|
||||
@ -131,7 +131,7 @@ export default class ArchiveController extends Controller {
|
||||
|
||||
async deleteFeature(
|
||||
req: IAuthRequest<{ featureName: string }>,
|
||||
res: Response<EmptySchema>,
|
||||
res: Response<void>,
|
||||
): Promise<void> {
|
||||
const { featureName } = req.params;
|
||||
const user = extractUsername(req);
|
||||
@ -141,7 +141,7 @@ export default class ArchiveController extends Controller {
|
||||
|
||||
async reviveFeature(
|
||||
req: IAuthRequest<{ featureName: string }>,
|
||||
res: Response<EmptySchema>,
|
||||
res: Response<void>,
|
||||
): Promise<void> {
|
||||
const userName = extractUsername(req);
|
||||
const { featureName } = req.params;
|
||||
|
@ -1,73 +0,0 @@
|
||||
import { Request, Response } from 'express';
|
||||
import Controller from '../controller';
|
||||
import { IUnleashServices } from '../../types/services';
|
||||
import { IUnleashConfig } from '../../types/option';
|
||||
import { ISortOrder } from '../../types/model';
|
||||
import EnvironmentService from '../../services/environment-service';
|
||||
import { Logger } from '../../logger';
|
||||
import { ADMIN } from '../../types/permissions';
|
||||
|
||||
interface EnvironmentParam {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export class EnvironmentsController extends Controller {
|
||||
private logger: Logger;
|
||||
|
||||
private service: EnvironmentService;
|
||||
|
||||
constructor(
|
||||
config: IUnleashConfig,
|
||||
{ environmentService }: Pick<IUnleashServices, 'environmentService'>,
|
||||
) {
|
||||
super(config);
|
||||
this.logger = config.getLogger('admin-api/environments-controller.ts');
|
||||
this.service = environmentService;
|
||||
this.get('/', this.getAll);
|
||||
this.put('/sort-order', this.updateSortOrder, ADMIN);
|
||||
this.get('/:name', this.getEnv);
|
||||
this.post('/:name/on', this.toggleEnvironmentOn, ADMIN);
|
||||
this.post('/:name/off', this.toggleEnvironmentOff, ADMIN);
|
||||
}
|
||||
|
||||
async getAll(req: Request, res: Response): Promise<void> {
|
||||
const environments = await this.service.getAll();
|
||||
res.status(200).json({ version: 1, environments });
|
||||
}
|
||||
|
||||
async updateSortOrder(
|
||||
req: Request<any, any, ISortOrder, any>,
|
||||
res: Response,
|
||||
): Promise<void> {
|
||||
await this.service.updateSortOrder(req.body);
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
async toggleEnvironmentOn(
|
||||
req: Request<EnvironmentParam, any, any, any>,
|
||||
res: Response,
|
||||
): Promise<void> {
|
||||
const { name } = req.params;
|
||||
await this.service.toggleEnvironment(name, true);
|
||||
res.status(204).end();
|
||||
}
|
||||
|
||||
async toggleEnvironmentOff(
|
||||
req: Request<EnvironmentParam, any, any, any>,
|
||||
res: Response,
|
||||
): Promise<void> {
|
||||
const { name } = req.params;
|
||||
await this.service.toggleEnvironment(name, false);
|
||||
res.status(204).end();
|
||||
}
|
||||
|
||||
async getEnv(
|
||||
req: Request<EnvironmentParam, any, any, any>,
|
||||
res: Response,
|
||||
): Promise<void> {
|
||||
const { name } = req.params;
|
||||
|
||||
const env = await this.service.get(name);
|
||||
res.status(200).json(env);
|
||||
}
|
||||
}
|
169
src/lib/routes/admin-api/environments.ts
Normal file
169
src/lib/routes/admin-api/environments.ts
Normal file
@ -0,0 +1,169 @@
|
||||
import { Request, Response } from 'express';
|
||||
import Controller from '../controller';
|
||||
import { IUnleashServices } from '../../types/services';
|
||||
import { IUnleashConfig } from '../../types/option';
|
||||
import EnvironmentService from '../../services/environment-service';
|
||||
import { Logger } from '../../logger';
|
||||
import { ADMIN, NONE } from '../../types/permissions';
|
||||
import { OpenApiService } from '../../services/openapi-service';
|
||||
import { createRequestSchema, createResponseSchema } from '../../openapi';
|
||||
import {
|
||||
environmentsSchema,
|
||||
EnvironmentsSchema,
|
||||
} from '../../openapi/spec/environments-schema';
|
||||
import {
|
||||
environmentSchema,
|
||||
EnvironmentSchema,
|
||||
} from '../../openapi/spec/environment-schema';
|
||||
import { SortOrderSchema } from '../../openapi/spec/sort-order-schema';
|
||||
import { emptyResponse } from '../../openapi/spec/empty-response';
|
||||
|
||||
interface EnvironmentParam {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export class EnvironmentsController extends Controller {
|
||||
private logger: Logger;
|
||||
|
||||
private openApiService: OpenApiService;
|
||||
|
||||
private service: EnvironmentService;
|
||||
|
||||
constructor(
|
||||
config: IUnleashConfig,
|
||||
{
|
||||
environmentService,
|
||||
openApiService,
|
||||
}: Pick<IUnleashServices, 'environmentService' | 'openApiService'>,
|
||||
) {
|
||||
super(config);
|
||||
this.logger = config.getLogger('admin-api/environments-controller.ts');
|
||||
this.openApiService = openApiService;
|
||||
this.service = environmentService;
|
||||
|
||||
this.route({
|
||||
method: 'get',
|
||||
path: '',
|
||||
handler: this.getAllEnvironments,
|
||||
permission: NONE,
|
||||
middleware: [
|
||||
openApiService.validPath({
|
||||
tags: ['admin'],
|
||||
operationId: 'getAllEnvironments',
|
||||
responses: { 200: emptyResponse },
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
this.route({
|
||||
method: 'get',
|
||||
path: '/:name',
|
||||
handler: this.getEnvironment,
|
||||
permission: NONE,
|
||||
middleware: [
|
||||
openApiService.validPath({
|
||||
tags: ['admin'],
|
||||
operationId: 'getEnvironment',
|
||||
responses: {
|
||||
200: createResponseSchema('environmentSchema'),
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
this.route({
|
||||
method: 'put',
|
||||
path: '/sort-order',
|
||||
handler: this.updateSortOrder,
|
||||
permission: ADMIN,
|
||||
middleware: [
|
||||
openApiService.validPath({
|
||||
tags: ['admin'],
|
||||
operationId: 'updateSortOrder',
|
||||
requestBody: createRequestSchema('sortOrderSchema'),
|
||||
responses: { 200: emptyResponse },
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
this.route({
|
||||
method: 'post',
|
||||
path: '/:name/on',
|
||||
acceptAnyContentType: true,
|
||||
handler: this.toggleEnvironmentOn,
|
||||
permission: ADMIN,
|
||||
middleware: [
|
||||
openApiService.validPath({
|
||||
tags: ['admin'],
|
||||
operationId: 'toggleEnvironmentOn',
|
||||
responses: { 200: emptyResponse },
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
this.route({
|
||||
method: 'post',
|
||||
path: '/:name/off',
|
||||
acceptAnyContentType: true,
|
||||
handler: this.toggleEnvironmentOff,
|
||||
permission: ADMIN,
|
||||
middleware: [
|
||||
openApiService.validPath({
|
||||
tags: ['admin'],
|
||||
operationId: 'toggleEnvironmentOff',
|
||||
responses: { 200: emptyResponse },
|
||||
}),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
async getAllEnvironments(
|
||||
req: Request,
|
||||
res: Response<EnvironmentsSchema>,
|
||||
): Promise<void> {
|
||||
this.openApiService.respondWithValidation(
|
||||
200,
|
||||
res,
|
||||
environmentsSchema.$id,
|
||||
{ version: 1, environments: await this.service.getAll() },
|
||||
);
|
||||
}
|
||||
|
||||
async updateSortOrder(
|
||||
req: Request<unknown, unknown, SortOrderSchema>,
|
||||
res: Response,
|
||||
): Promise<void> {
|
||||
await this.service.updateSortOrder(req.body);
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
async toggleEnvironmentOn(
|
||||
req: Request<EnvironmentParam>,
|
||||
res: Response,
|
||||
): Promise<void> {
|
||||
const { name } = req.params;
|
||||
await this.service.toggleEnvironment(name, true);
|
||||
res.status(204).end();
|
||||
}
|
||||
|
||||
async toggleEnvironmentOff(
|
||||
req: Request<EnvironmentParam>,
|
||||
res: Response,
|
||||
): Promise<void> {
|
||||
const { name } = req.params;
|
||||
await this.service.toggleEnvironment(name, false);
|
||||
res.status(204).end();
|
||||
}
|
||||
|
||||
async getEnvironment(
|
||||
req: Request<EnvironmentParam>,
|
||||
res: Response<EnvironmentSchema>,
|
||||
): Promise<void> {
|
||||
this.openApiService.respondWithValidation(
|
||||
200,
|
||||
res,
|
||||
environmentSchema.$id,
|
||||
await this.service.get(req.params.name),
|
||||
);
|
||||
}
|
||||
}
|
@ -25,6 +25,7 @@ import { TagsSchema } from '../../openapi/spec/tags-schema';
|
||||
import { serializeDates } from '../../types/serialize-dates';
|
||||
import { OpenApiService } from '../../services/openapi-service';
|
||||
import { createRequestSchema, createResponseSchema } from '../../openapi';
|
||||
import { emptyResponse } from '../../openapi/spec/empty-response';
|
||||
|
||||
const version = 1;
|
||||
|
||||
@ -92,7 +93,7 @@ class FeatureController extends Controller {
|
||||
openApiService.validPath({
|
||||
tags: ['admin'],
|
||||
operationId: 'validateFeature',
|
||||
responses: { 200: createResponseSchema('emptySchema') },
|
||||
responses: { 200: emptyResponse },
|
||||
}),
|
||||
],
|
||||
});
|
||||
@ -136,7 +137,7 @@ class FeatureController extends Controller {
|
||||
openApiService.validPath({
|
||||
tags: ['admin'],
|
||||
operationId: 'removeTag',
|
||||
responses: { 200: createResponseSchema('emptySchema') },
|
||||
responses: { 200: emptyResponse },
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
@ -12,18 +12,18 @@ import UserController from './user';
|
||||
import ConfigController from './config';
|
||||
import ContextController from './context';
|
||||
import ClientMetricsController from './client-metrics';
|
||||
import BootstrapController from './bootstrap-controller';
|
||||
import BootstrapController from './bootstrap';
|
||||
import StateController from './state';
|
||||
import TagController from './tag';
|
||||
import TagTypeController from './tag-type';
|
||||
import AddonController from './addon';
|
||||
import ApiTokenController from './api-token-controller';
|
||||
import ApiTokenController from './api-token';
|
||||
import UserAdminController from './user-admin';
|
||||
import EmailController from './email';
|
||||
import UserFeedbackController from './user-feedback-controller';
|
||||
import UserSplashController from './user-splash-controller';
|
||||
import UserFeedbackController from './user-feedback';
|
||||
import UserSplashController from './user-splash';
|
||||
import ProjectApi from './project';
|
||||
import { EnvironmentsController } from './environments-controller';
|
||||
import { EnvironmentsController } from './environments';
|
||||
import ConstraintsController from './constraints';
|
||||
|
||||
class AdminApi extends Controller {
|
||||
|
@ -5,8 +5,9 @@ import { IUnleashServices } from '../../../types/services';
|
||||
import { Logger } from '../../../logger';
|
||||
import EnvironmentService from '../../../services/environment-service';
|
||||
import { UPDATE_PROJECT } from '../../../types/permissions';
|
||||
import { createRequestSchema, createResponseSchema } from '../../../openapi';
|
||||
import { createRequestSchema } from '../../../openapi';
|
||||
import { ProjectEnvironmentSchema } from '../../../openapi/spec/project-environment-schema';
|
||||
import { emptyResponse } from '../../../openapi/spec/empty-response';
|
||||
|
||||
const PREFIX = '/:projectId/environments';
|
||||
|
||||
@ -44,7 +45,7 @@ export default class EnvironmentsController extends Controller {
|
||||
requestBody: createRequestSchema(
|
||||
'projectEnvironmentSchema',
|
||||
),
|
||||
responses: { 200: createResponseSchema('emptySchema') },
|
||||
responses: { 200: emptyResponse },
|
||||
}),
|
||||
],
|
||||
});
|
||||
@ -59,7 +60,7 @@ export default class EnvironmentsController extends Controller {
|
||||
openApiService.validPath({
|
||||
tags: ['admin'],
|
||||
operationId: 'removeEnvironmentFromProject',
|
||||
responses: { 200: createResponseSchema('emptySchema') },
|
||||
responses: { 200: emptyResponse },
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
@ -35,6 +35,7 @@ import { serializeDates } from '../../../types/serialize-dates';
|
||||
import { OpenApiService } from '../../../services/openapi-service';
|
||||
import { createRequestSchema, createResponseSchema } from '../../../openapi';
|
||||
import { FeatureEnvironmentSchema } from '../../../openapi/spec/feature-environment-schema';
|
||||
import { emptyResponse } from '../../../openapi/spec/empty-response';
|
||||
|
||||
interface FeatureStrategyParams {
|
||||
projectId: string;
|
||||
@ -216,7 +217,7 @@ export default class ProjectFeaturesController extends Controller {
|
||||
openApiService.validPath({
|
||||
operationId: 'deleteStrategy',
|
||||
tags: ['admin'],
|
||||
responses: { 200: createResponseSchema('emptySchema') },
|
||||
responses: { 200: emptyResponse },
|
||||
}),
|
||||
],
|
||||
});
|
||||
@ -322,7 +323,7 @@ export default class ProjectFeaturesController extends Controller {
|
||||
openApiService.validPath({
|
||||
tags: ['admin'],
|
||||
operationId: 'archiveFeature',
|
||||
responses: { 200: createResponseSchema('emptySchema') },
|
||||
responses: { 200: emptyResponse },
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
@ -5,11 +5,12 @@ import {
|
||||
AdminApiOperation,
|
||||
ClientApiOperation,
|
||||
createOpenApiSchema,
|
||||
JsonSchemaProps,
|
||||
removeJsonSchemaProps,
|
||||
SchemaId,
|
||||
} from '../openapi';
|
||||
import { Logger } from '../logger';
|
||||
import { validateSchema } from '../openapi/validate';
|
||||
import { omitKeys } from '../util/omit-keys';
|
||||
|
||||
export class OpenApiService {
|
||||
private readonly config: IUnleashConfig;
|
||||
@ -43,11 +44,11 @@ export class OpenApiService {
|
||||
return `${baseUriPath}/docs/openapi`;
|
||||
}
|
||||
|
||||
registerCustomSchemas<T extends object>(schemas: {
|
||||
[name: string]: { $id: string; components: T };
|
||||
}): void {
|
||||
registerCustomSchemas<T extends JsonSchemaProps>(
|
||||
schemas: Record<string, T>,
|
||||
): void {
|
||||
Object.entries(schemas).forEach(([name, schema]) => {
|
||||
this.api.schema(name, omitKeys(schema, '$id', 'components'));
|
||||
this.api.schema(name, removeJsonSchemaProps(schema));
|
||||
});
|
||||
}
|
||||
|
||||
@ -73,12 +74,11 @@ export class OpenApiService {
|
||||
const errors = validateSchema(schema, data);
|
||||
|
||||
if (errors) {
|
||||
this.logger.warn(
|
||||
'Invalid response:',
|
||||
process.env.NODE_ENV === 'development'
|
||||
? JSON.stringify(errors, null, 2)
|
||||
: errors,
|
||||
);
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
throw new Error(JSON.stringify(errors, null, 2));
|
||||
} else {
|
||||
this.logger.warn('Invalid response:', errors);
|
||||
}
|
||||
}
|
||||
|
||||
res.status(status).json(data);
|
||||
|
@ -157,9 +157,6 @@ Object {
|
||||
],
|
||||
"type": "object",
|
||||
},
|
||||
"emptySchema": Object {
|
||||
"description": "emptySchema",
|
||||
},
|
||||
"environmentSchema": Object {
|
||||
"additionalProperties": false,
|
||||
"properties": Object {
|
||||
@ -169,6 +166,9 @@ Object {
|
||||
"name": Object {
|
||||
"type": "string",
|
||||
},
|
||||
"protected": Object {
|
||||
"type": "boolean",
|
||||
},
|
||||
"sortOrder": Object {
|
||||
"type": "number",
|
||||
},
|
||||
@ -183,6 +183,25 @@ Object {
|
||||
],
|
||||
"type": "object",
|
||||
},
|
||||
"environmentsSchema": Object {
|
||||
"additionalProperties": false,
|
||||
"properties": Object {
|
||||
"environments": Object {
|
||||
"items": Object {
|
||||
"$ref": "#/components/schemas/environmentSchema",
|
||||
},
|
||||
"type": "array",
|
||||
},
|
||||
"version": Object {
|
||||
"type": "integer",
|
||||
},
|
||||
},
|
||||
"required": Array [
|
||||
"version",
|
||||
"environments",
|
||||
],
|
||||
"type": "object",
|
||||
},
|
||||
"featureEnvironmentSchema": Object {
|
||||
"additionalProperties": false,
|
||||
"properties": Object {
|
||||
@ -624,6 +643,12 @@ Object {
|
||||
],
|
||||
"type": "object",
|
||||
},
|
||||
"sortOrderSchema": Object {
|
||||
"additionalProperties": Object {
|
||||
"type": "number",
|
||||
},
|
||||
"type": "object",
|
||||
},
|
||||
"strategySchema": Object {
|
||||
"additionalProperties": false,
|
||||
"properties": Object {
|
||||
@ -986,14 +1011,7 @@ Object {
|
||||
],
|
||||
"responses": Object {
|
||||
"200": Object {
|
||||
"content": Object {
|
||||
"application/json": Object {
|
||||
"schema": Object {
|
||||
"$ref": "#/components/schemas/emptySchema",
|
||||
},
|
||||
},
|
||||
},
|
||||
"description": "emptySchema",
|
||||
"description": "emptyResponse",
|
||||
},
|
||||
},
|
||||
"tags": Array [
|
||||
@ -1016,14 +1034,7 @@ Object {
|
||||
],
|
||||
"responses": Object {
|
||||
"200": Object {
|
||||
"content": Object {
|
||||
"application/json": Object {
|
||||
"schema": Object {
|
||||
"$ref": "#/components/schemas/emptySchema",
|
||||
},
|
||||
},
|
||||
},
|
||||
"description": "emptySchema",
|
||||
"description": "emptyResponse",
|
||||
},
|
||||
},
|
||||
"tags": Array [
|
||||
@ -1058,6 +1069,119 @@ Object {
|
||||
],
|
||||
},
|
||||
},
|
||||
"/api/admin/environments": Object {
|
||||
"get": Object {
|
||||
"operationId": "getAllEnvironments",
|
||||
"responses": Object {
|
||||
"200": Object {
|
||||
"description": "emptyResponse",
|
||||
},
|
||||
},
|
||||
"tags": Array [
|
||||
"admin",
|
||||
],
|
||||
},
|
||||
},
|
||||
"/api/admin/environments/sort-order": Object {
|
||||
"put": Object {
|
||||
"operationId": "updateSortOrder",
|
||||
"requestBody": Object {
|
||||
"content": Object {
|
||||
"application/json": Object {
|
||||
"schema": Object {
|
||||
"$ref": "#/components/schemas/sortOrderSchema",
|
||||
},
|
||||
},
|
||||
},
|
||||
"description": "sortOrderSchema",
|
||||
"required": true,
|
||||
},
|
||||
"responses": Object {
|
||||
"200": Object {
|
||||
"description": "emptyResponse",
|
||||
},
|
||||
},
|
||||
"tags": Array [
|
||||
"admin",
|
||||
],
|
||||
},
|
||||
},
|
||||
"/api/admin/environments/{name}": Object {
|
||||
"get": Object {
|
||||
"operationId": "getEnvironment",
|
||||
"parameters": Array [
|
||||
Object {
|
||||
"in": "path",
|
||||
"name": "name",
|
||||
"required": true,
|
||||
"schema": Object {
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
],
|
||||
"responses": Object {
|
||||
"200": Object {
|
||||
"content": Object {
|
||||
"application/json": Object {
|
||||
"schema": Object {
|
||||
"$ref": "#/components/schemas/environmentSchema",
|
||||
},
|
||||
},
|
||||
},
|
||||
"description": "environmentSchema",
|
||||
},
|
||||
},
|
||||
"tags": Array [
|
||||
"admin",
|
||||
],
|
||||
},
|
||||
},
|
||||
"/api/admin/environments/{name}/off": Object {
|
||||
"post": Object {
|
||||
"operationId": "toggleEnvironmentOff",
|
||||
"parameters": Array [
|
||||
Object {
|
||||
"in": "path",
|
||||
"name": "name",
|
||||
"required": true,
|
||||
"schema": Object {
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
],
|
||||
"responses": Object {
|
||||
"200": Object {
|
||||
"description": "emptyResponse",
|
||||
},
|
||||
},
|
||||
"tags": Array [
|
||||
"admin",
|
||||
],
|
||||
},
|
||||
},
|
||||
"/api/admin/environments/{name}/on": Object {
|
||||
"post": Object {
|
||||
"operationId": "toggleEnvironmentOn",
|
||||
"parameters": Array [
|
||||
Object {
|
||||
"in": "path",
|
||||
"name": "name",
|
||||
"required": true,
|
||||
"schema": Object {
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
],
|
||||
"responses": Object {
|
||||
"200": Object {
|
||||
"description": "emptyResponse",
|
||||
},
|
||||
},
|
||||
"tags": Array [
|
||||
"admin",
|
||||
],
|
||||
},
|
||||
},
|
||||
"/api/admin/feature-types": Object {
|
||||
"get": Object {
|
||||
"operationId": "getAllFeatureTypes",
|
||||
@ -1104,14 +1228,7 @@ Object {
|
||||
"operationId": "validateFeature",
|
||||
"responses": Object {
|
||||
"200": Object {
|
||||
"content": Object {
|
||||
"application/json": Object {
|
||||
"schema": Object {
|
||||
"$ref": "#/components/schemas/emptySchema",
|
||||
},
|
||||
},
|
||||
},
|
||||
"description": "emptySchema",
|
||||
"description": "emptyResponse",
|
||||
},
|
||||
},
|
||||
"tags": Array [
|
||||
@ -1219,14 +1336,7 @@ Object {
|
||||
],
|
||||
"responses": Object {
|
||||
"200": Object {
|
||||
"content": Object {
|
||||
"application/json": Object {
|
||||
"schema": Object {
|
||||
"$ref": "#/components/schemas/emptySchema",
|
||||
},
|
||||
},
|
||||
},
|
||||
"description": "emptySchema",
|
||||
"description": "emptyResponse",
|
||||
},
|
||||
},
|
||||
"tags": Array [
|
||||
@ -1310,14 +1420,7 @@ Object {
|
||||
},
|
||||
"responses": Object {
|
||||
"200": Object {
|
||||
"content": Object {
|
||||
"application/json": Object {
|
||||
"schema": Object {
|
||||
"$ref": "#/components/schemas/emptySchema",
|
||||
},
|
||||
},
|
||||
},
|
||||
"description": "emptySchema",
|
||||
"description": "emptyResponse",
|
||||
},
|
||||
},
|
||||
"tags": Array [
|
||||
@ -1348,14 +1451,7 @@ Object {
|
||||
],
|
||||
"responses": Object {
|
||||
"200": Object {
|
||||
"content": Object {
|
||||
"application/json": Object {
|
||||
"schema": Object {
|
||||
"$ref": "#/components/schemas/emptySchema",
|
||||
},
|
||||
},
|
||||
},
|
||||
"description": "emptySchema",
|
||||
"description": "emptyResponse",
|
||||
},
|
||||
},
|
||||
"tags": Array [
|
||||
@ -1455,14 +1551,7 @@ Object {
|
||||
],
|
||||
"responses": Object {
|
||||
"200": Object {
|
||||
"content": Object {
|
||||
"application/json": Object {
|
||||
"schema": Object {
|
||||
"$ref": "#/components/schemas/emptySchema",
|
||||
},
|
||||
},
|
||||
},
|
||||
"description": "emptySchema",
|
||||
"description": "emptyResponse",
|
||||
},
|
||||
},
|
||||
"tags": Array [
|
||||
@ -1927,14 +2016,7 @@ Object {
|
||||
],
|
||||
"responses": Object {
|
||||
"200": Object {
|
||||
"content": Object {
|
||||
"application/json": Object {
|
||||
"schema": Object {
|
||||
"$ref": "#/components/schemas/emptySchema",
|
||||
},
|
||||
},
|
||||
},
|
||||
"description": "emptySchema",
|
||||
"description": "emptyResponse",
|
||||
},
|
||||
},
|
||||
"tags": Array [
|
||||
|
Loading…
Reference in New Issue
Block a user