From 789500260272bf306def6d6e772da140611a6420 Mon Sep 17 00:00:00 2001 From: Christopher Kolstad Date: Fri, 3 Jun 2022 13:16:59 +0200 Subject: [PATCH] feat: add OpenApi spec to feature variants (#1659) * feat: add OpenApi spec to feature variants --- .../openapi/spec/feature-variants-response.ts | 12 ++ .../openapi/spec/feature-variants-schema.ts | 26 ++++ .../spec/update-feature-variants-request.ts | 15 ++ src/lib/routes/admin-api/project/variants.ts | 61 ++++++-- .../api/admin/project/variants.e2e.test.ts | 1 + .../__snapshots__/openapi.e2e.test.ts.snap | 136 ++++++++++++++++++ 6 files changed, 242 insertions(+), 9 deletions(-) create mode 100644 src/lib/openapi/spec/feature-variants-response.ts create mode 100644 src/lib/openapi/spec/feature-variants-schema.ts create mode 100644 src/lib/openapi/spec/update-feature-variants-request.ts diff --git a/src/lib/openapi/spec/feature-variants-response.ts b/src/lib/openapi/spec/feature-variants-response.ts new file mode 100644 index 0000000000..504ddc6882 --- /dev/null +++ b/src/lib/openapi/spec/feature-variants-response.ts @@ -0,0 +1,12 @@ +import { OpenAPIV3 } from 'openapi-types'; + +export const featureVariantsResponse: OpenAPIV3.ResponseObject = { + description: 'featureVariantResponse', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/featureVariantsSchema', + }, + }, + }, +}; diff --git a/src/lib/openapi/spec/feature-variants-schema.ts b/src/lib/openapi/spec/feature-variants-schema.ts new file mode 100644 index 0000000000..0031552c38 --- /dev/null +++ b/src/lib/openapi/spec/feature-variants-schema.ts @@ -0,0 +1,26 @@ +import { createSchemaObject, CreateSchemaType } from '../types'; +import { variantSchema } from './variant-schema'; + +const schema = { + type: 'object', + additionalProperties: false, + required: ['version', 'variants'], + properties: { + version: { + type: 'integer', + }, + variants: { + type: 'array', + items: { + $ref: '#/components/schemas/variantSchema', + }, + }, + }, + 'components/schemas': { + variantSchema, + }, +}; + +export type FeatureVariantsSchema = CreateSchemaType; + +export const featureVariantsSchema = createSchemaObject(schema); diff --git a/src/lib/openapi/spec/update-feature-variants-request.ts b/src/lib/openapi/spec/update-feature-variants-request.ts new file mode 100644 index 0000000000..c416dc6bca --- /dev/null +++ b/src/lib/openapi/spec/update-feature-variants-request.ts @@ -0,0 +1,15 @@ +import { OpenAPIV3 } from 'openapi-types'; + +export const updateFeatureVariantsRequest: OpenAPIV3.RequestBodyObject = { + required: true, + content: { + 'application/json': { + schema: { + type: 'array', + items: { + $ref: '#/components/schemas/variantSchema', + }, + }, + }, + }, +}; diff --git a/src/lib/routes/admin-api/project/variants.ts b/src/lib/routes/admin-api/project/variants.ts index a704ed8e86..6d25c5355d 100644 --- a/src/lib/routes/admin-api/project/variants.ts +++ b/src/lib/routes/admin-api/project/variants.ts @@ -5,10 +5,14 @@ import { IUnleashConfig } from '../../../types/option'; import { IUnleashServices } from '../../../types'; import { Request, Response } from 'express'; import { Operation } from 'fast-json-patch'; -import { UPDATE_FEATURE_VARIANTS } from '../../../types/permissions'; +import { NONE, UPDATE_FEATURE_VARIANTS } from '../../../types/permissions'; import { IVariant } from '../../../types/model'; import { extractUsername } from '../../../util/extract-user'; import { IAuthRequest } from '../../unleash-types'; +import { featureVariantsResponse } from '../../../openapi/spec/feature-variants-response'; +import { patchRequest } from '../../../openapi/spec/patch-request'; +import { updateFeatureVariantsRequest } from '../../../openapi/spec/update-feature-variants-request'; +import { FeatureVariantsSchema } from '../../../openapi/spec/feature-variants-schema'; const PREFIX = '/:projectId/features/:featureName/variants'; @@ -19,7 +23,6 @@ interface FeatureParams extends ProjectParam { interface ProjectParam { projectId: string; } - export default class VariantsController extends Controller { private logger: Logger; @@ -29,19 +32,59 @@ export default class VariantsController extends Controller { config: IUnleashConfig, { featureToggleService, - }: Pick, + openApiService, + }: Pick, ) { super(config); this.logger = config.getLogger('admin-api/project/variants.ts'); this.featureService = featureToggleService; - this.get(PREFIX, this.getVariants); - this.patch(PREFIX, this.patchVariants, UPDATE_FEATURE_VARIANTS); - this.put(PREFIX, this.overwriteVariants, UPDATE_FEATURE_VARIANTS); + this.route({ + method: 'get', + path: PREFIX, + permission: NONE, + acceptAnyContentType: true, + handler: this.getVariants, + middleware: [ + openApiService.validPath({ + tags: ['admin'], + operationId: 'getFeatureVariants', + responses: { 200: featureVariantsResponse }, + }), + ], + }); + this.route({ + method: 'patch', + path: PREFIX, + permission: UPDATE_FEATURE_VARIANTS, + handler: this.patchVariants, + middleware: [ + openApiService.validPath({ + tags: ['admin'], + operationId: 'patchFeatureVariants', + requestBody: patchRequest, + responses: { 200: featureVariantsResponse }, + }), + ], + }); + this.route({ + method: 'put', + path: PREFIX, + permission: UPDATE_FEATURE_VARIANTS, + handler: this.overwriteVariants, + middleware: [ + openApiService.validPath({ + tags: ['admin'], + operationId: 'overwriteFeatureVariants', + requestBody: updateFeatureVariantsRequest, + responses: { 200: featureVariantsResponse }, + }), + ], + }); } async getVariants( req: Request, - res: Response, + res: Response, ): Promise { const { featureName } = req.params; const variants = await this.featureService.getVariants(featureName); @@ -50,7 +93,7 @@ export default class VariantsController extends Controller { async patchVariants( req: IAuthRequest, - res: Response, + res: Response, ): Promise { const { projectId, featureName } = req.params; const userName = extractUsername(req); @@ -69,7 +112,7 @@ export default class VariantsController extends Controller { async overwriteVariants( req: IAuthRequest, - res: Response, + res: Response, ): Promise { const { projectId, featureName } = req.params; const userName = extractUsername(req); diff --git a/src/test/e2e/api/admin/project/variants.e2e.test.ts b/src/test/e2e/api/admin/project/variants.e2e.test.ts index a741832502..b5370aa601 100644 --- a/src/test/e2e/api/admin/project/variants.e2e.test.ts +++ b/src/test/e2e/api/admin/project/variants.e2e.test.ts @@ -266,6 +266,7 @@ test('PUTing an invalid variant throws 400 exception', async () => { name: 'variant', weight: 500, weightType: 'party', + stickiness: 'userId', }, ]; await app.request diff --git a/src/test/e2e/api/openapi/__snapshots__/openapi.e2e.test.ts.snap b/src/test/e2e/api/openapi/__snapshots__/openapi.e2e.test.ts.snap index 775f23613b..659598d9c4 100644 --- a/src/test/e2e/api/openapi/__snapshots__/openapi.e2e.test.ts.snap +++ b/src/test/e2e/api/openapi/__snapshots__/openapi.e2e.test.ts.snap @@ -1515,6 +1515,142 @@ Object { ], }, }, + "/api/admin/projects/{projectId}/features/{featureName}/variants": Object { + "get": Object { + "operationId": "getFeatureVariants", + "parameters": Array [ + Object { + "in": "path", + "name": "projectId", + "required": true, + "schema": Object { + "type": "string", + }, + }, + Object { + "in": "path", + "name": "featureName", + "required": true, + "schema": Object { + "type": "string", + }, + }, + ], + "responses": Object { + "200": Object { + "content": Object { + "application/json": Object { + "schema": Object { + "$ref": "#/components/schemas/featureVariantsSchema", + }, + }, + }, + "description": "featureVariantResponse", + }, + }, + "tags": Array [ + "admin", + ], + }, + "patch": Object { + "operationId": "patchFeatureVariants", + "parameters": Array [ + Object { + "in": "path", + "name": "projectId", + "required": true, + "schema": Object { + "type": "string", + }, + }, + Object { + "in": "path", + "name": "featureName", + "required": true, + "schema": Object { + "type": "string", + }, + }, + ], + "requestBody": Object { + "content": Object { + "application/json": Object { + "schema": Object { + "items": Object { + "$ref": "#/components/schemas/patchOperationSchema", + }, + "type": "array", + }, + }, + }, + "required": true, + }, + "responses": Object { + "200": Object { + "content": Object { + "application/json": Object { + "schema": Object { + "$ref": "#/components/schemas/featureVariantsSchema", + }, + }, + }, + "description": "featureVariantResponse", + }, + }, + "tags": Array [ + "admin", + ], + }, + "put": Object { + "operationId": "overwriteFeatureVariants", + "parameters": Array [ + Object { + "in": "path", + "name": "projectId", + "required": true, + "schema": Object { + "type": "string", + }, + }, + Object { + "in": "path", + "name": "featureName", + "required": true, + "schema": Object { + "type": "string", + }, + }, + ], + "requestBody": Object { + "content": Object { + "application/json": Object { + "schema": Object { + "items": Object { + "$ref": "#/components/schemas/variantSchema", + }, + "type": "array", + }, + }, + }, + "required": true, + }, + "responses": Object { + "200": Object { + "content": Object { + "application/json": Object { + "schema": Object { + "$ref": "#/components/schemas/featureVariantsSchema", + }, + }, + }, + "description": "featureVariantResponse", + }, + }, + "tags": Array [ + "admin", + ], + }, + }, }, "security": Array [ Object {