1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-05-22 01:16:07 +02:00

refactor: improve OpenAPI refs (#1620)

* refactor: simplify FeatureEnvironmentSchema name

* refactor: format schema files

* fix: pass nested schemas to FromSchema

* refactor: remove ref order note

* refactor: fix overly strict required fields

* refactor: clean up mapper names and paths

* refactor: replace mappers with optional fields
This commit is contained in:
olav 2022-05-24 08:37:35 +02:00 committed by GitHub
parent d4581a1ae2
commit 59060ed3ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 98 additions and 170 deletions

View File

@ -10,7 +10,7 @@ import { tagsResponseSchema } from './spec/tags-response-schema';
import { createStrategySchema } from './spec/create-strategy-schema'; import { createStrategySchema } from './spec/create-strategy-schema';
import { featureSchema } from './spec/feature-schema'; import { featureSchema } from './spec/feature-schema';
import { parametersSchema } from './spec/parameters-schema'; import { parametersSchema } from './spec/parameters-schema';
import { featureEnvironmentInfoSchema } from './spec/feature-environment-info-schema'; import { featureEnvironmentSchema } from './spec/feature-environment-schema';
import { emptyResponseSchema } from './spec/empty-response-schema'; import { emptyResponseSchema } from './spec/empty-response-schema';
import { patchOperationSchema } from './spec/patch-operation-schema'; import { patchOperationSchema } from './spec/patch-operation-schema';
import { updateFeatureSchema } from './spec/updateFeatureSchema'; import { updateFeatureSchema } from './spec/updateFeatureSchema';
@ -48,7 +48,7 @@ export const createOpenApiSchema = (
createStrategySchema, createStrategySchema,
featureSchema, featureSchema,
featuresSchema, featuresSchema,
featureEnvironmentInfoSchema, featureEnvironmentSchema,
featureStrategySchema, featureStrategySchema,
emptyResponseSchema, emptyResponseSchema,
overrideSchema, overrideSchema,

View File

@ -1,29 +0,0 @@
import { SchemaMapper } from './mapper';
import { IFeatureEnvironmentInfo } from '../../types/model';
import { FeatureEnvironmentInfoSchema } from '../spec/feature-environment-info-schema';
import { FeatureStrategyMapper } from './feature-strategy.mapper';
export class EnvironmentInfoMapper
implements
SchemaMapper<
FeatureEnvironmentInfoSchema,
IFeatureEnvironmentInfo,
Partial<FeatureEnvironmentInfoSchema>
>
{
private mapper = new FeatureStrategyMapper();
fromPublic(input: FeatureEnvironmentInfoSchema): IFeatureEnvironmentInfo {
return {
...input,
strategies: input.strategies.map(this.mapper.fromPublic),
};
}
toPublic(input: IFeatureEnvironmentInfo): FeatureEnvironmentInfoSchema {
return {
...input,
strategies: input.strategies.map(this.mapper.toPublic),
};
}
}

View File

@ -1,29 +0,0 @@
import { SchemaMapper } from './mapper';
import { IFeatureStrategy } from '../../types/model';
import { CreateStrategySchema } from '../spec/create-strategy-schema';
import { UpdateStrategySchema } from '../spec/update-strategy-schema';
import { FeatureStrategySchema } from '../spec/feature-strategy-schema';
export class FeatureStrategyMapper
implements
SchemaMapper<
FeatureStrategySchema,
IFeatureStrategy,
CreateStrategySchema | UpdateStrategySchema
>
{
fromPublic(input: FeatureStrategySchema): IFeatureStrategy {
return {
...input,
id: input.id || '',
projectId: input.projectId! || '',
};
}
toPublic(input: IFeatureStrategy): FeatureStrategySchema {
return {
...input,
name: input.strategyName,
};
}
}

View File

@ -1,3 +0,0 @@
export * from './environment-info.mapper';
export * from './feature-strategy.mapper';
export * from './strategy.mapper';

View File

@ -1,7 +0,0 @@
// Convert between public schema types and internal data types.
// Avoids coupling public schemas to internal implementation details.
export interface SchemaMapper<SCHEMA, INTERNAL, INPUT = Partial<SCHEMA>> {
fromPublic(input: SCHEMA): INTERNAL;
toPublic(input: INTERNAL): SCHEMA;
mapInput?(input: INPUT): INTERNAL;
}

View File

@ -1,33 +0,0 @@
import { SchemaMapper } from './mapper';
import { IStrategyConfig } from '../../types/model';
import { StrategySchema } from '../spec/strategy-schema';
import { CreateStrategySchema } from '../spec/create-strategy-schema';
import { UpdateStrategySchema } from '../spec/update-strategy-schema';
export class StrategyMapper
implements
SchemaMapper<
StrategySchema,
IStrategyConfig,
CreateStrategySchema | UpdateStrategySchema
>
{
fromPublic(input: StrategySchema): IStrategyConfig {
return input;
}
toPublic(input: IStrategyConfig): StrategySchema {
return input;
}
mapInput(
input: CreateStrategySchema | UpdateStrategySchema,
): IStrategyConfig {
return {
...input,
name: input.name || '',
parameters: input.parameters || {},
constraints: input.constraints || [],
};
}
}

View File

@ -5,6 +5,7 @@ import { constraintSchema } from './constraint-schema';
const schema = { const schema = {
type: 'object', type: 'object',
additionalProperties: false, additionalProperties: false,
required: ['name'],
properties: { properties: {
name: { name: {
type: 'string', type: 'string',
@ -14,7 +15,9 @@ const schema = {
}, },
constraints: { constraints: {
type: 'array', type: 'array',
items: { $ref: '#/components/schemas/constraintSchema' }, items: {
$ref: '#/components/schemas/constraintSchema',
},
}, },
parameters: { parameters: {
$ref: '#/components/schemas/parametersSchema', $ref: '#/components/schemas/parametersSchema',

View File

@ -1,12 +0,0 @@
import { OpenAPIV3 } from 'openapi-types';
export const featureEnvironmentInfoResponse: OpenAPIV3.ResponseObject = {
description: 'featureEnvironmentInfoResponse',
content: {
'application/json': {
schema: {
$ref: '#/components/schemas/featureEnvironmentInfoSchema',
},
},
},
};

View File

@ -0,0 +1,12 @@
import { OpenAPIV3 } from 'openapi-types';
export const featureEnvironmentResponse: OpenAPIV3.ResponseObject = {
description: 'featureEnvironmentResponse',
content: {
'application/json': {
schema: {
$ref: '#/components/schemas/featureEnvironmentSchema',
},
},
},
};

View File

@ -1,10 +1,12 @@
import { createSchemaObject, CreateSchemaType } from '../types'; import { createSchemaObject, CreateSchemaType } from '../types';
import { featureStrategySchema } from './feature-strategy-schema'; import { featureStrategySchema } from './feature-strategy-schema';
import { constraintSchema } from './constraint-schema';
import { parametersSchema } from './parameters-schema';
let schema = { let schema = {
type: 'object', type: 'object',
additionalProperties: false, additionalProperties: false,
required: ['name', 'environment', 'enabled', 'strategies'], required: ['name', 'enabled'],
properties: { properties: {
name: { name: {
type: 'string', type: 'string',
@ -27,9 +29,11 @@ let schema = {
}, },
'components/schemas': { 'components/schemas': {
featureStrategySchema, featureStrategySchema,
constraintSchema,
parametersSchema,
}, },
} as const; } as const;
export type FeatureEnvironmentInfoSchema = CreateSchemaType<typeof schema>; export type FeatureEnvironmentSchema = CreateSchemaType<typeof schema>;
export const featureEnvironmentInfoSchema = createSchemaObject(schema); export const featureEnvironmentSchema = createSchemaObject(schema);

View File

@ -1,12 +1,16 @@
import { createSchemaObject, CreateSchemaType } from '../types'; import { createSchemaObject, CreateSchemaType } from '../types';
import { strategySchema } from './strategy-schema'; import { strategySchema } from './strategy-schema';
import { variantSchema } from './variant-schema'; import { variantSchema } from './variant-schema';
import { featureEnvironmentInfoSchema } from './feature-environment-info-schema'; import { featureEnvironmentSchema } from './feature-environment-schema';
import { featureStrategySchema } from './feature-strategy-schema';
import { constraintSchema } from './constraint-schema';
import { parametersSchema } from './parameters-schema';
import { overrideSchema } from './override-schema';
const schema = { const schema = {
type: 'object', type: 'object',
additionalProperties: false, additionalProperties: false,
required: ['name', 'project'], required: ['name'],
properties: { properties: {
name: { name: {
type: 'string', type: 'string',
@ -45,12 +49,14 @@ const schema = {
environments: { environments: {
type: 'array', type: 'array',
items: { items: {
$ref: '#/components/schemas/featureEnvironmentInfoSchema', $ref: '#/components/schemas/featureEnvironmentSchema',
}, },
}, },
strategies: { strategies: {
type: 'array', type: 'array',
items: { $ref: '#/components/schemas/strategySchema' }, items: {
$ref: '#/components/schemas/strategySchema',
},
}, },
variants: { variants: {
type: 'array', type: 'array',
@ -60,7 +66,11 @@ const schema = {
}, },
}, },
'components/schemas': { 'components/schemas': {
featureEnvironmentInfoSchema, constraintSchema,
featureEnvironmentSchema,
featureStrategySchema,
overrideSchema,
parametersSchema,
strategySchema, strategySchema,
variantSchema, variantSchema,
}, },

View File

@ -7,7 +7,6 @@ export const schema = {
additionalProperties: false, additionalProperties: false,
required: [ required: [
'id', 'id',
'name',
'featureName', 'featureName',
'strategyName', 'strategyName',
'constraints', 'constraints',
@ -43,9 +42,13 @@ export const schema = {
}, },
constraints: { constraints: {
type: 'array', type: 'array',
items: { $ref: '#/components/schemas/constraintSchema' }, items: {
$ref: '#/components/schemas/constraintSchema',
},
},
parameters: {
$ref: '#/components/schemas/parametersSchema',
}, },
parameters: { $ref: '#/components/schemas/parametersSchema' },
}, },
'components/schemas': { 'components/schemas': {
constraintSchema, constraintSchema,

View File

@ -1,5 +1,12 @@
import { createSchemaObject, CreateSchemaType } from '../types'; import { createSchemaObject, CreateSchemaType } from '../types';
import { featureSchema } from './feature-schema'; import { featureSchema } from './feature-schema';
import { parametersSchema } from './parameters-schema';
import { variantSchema } from './variant-schema';
import { overrideSchema } from './override-schema';
import { featureEnvironmentSchema } from './feature-environment-schema';
import { featureStrategySchema } from './feature-strategy-schema';
import { constraintSchema } from './constraint-schema';
import { strategySchema } from './strategy-schema';
const schema = { const schema = {
type: 'object', type: 'object',
@ -11,11 +18,20 @@ const schema = {
}, },
features: { features: {
type: 'array', type: 'array',
items: { $ref: '#/components/schemas/featureSchema' }, items: {
$ref: '#/components/schemas/featureSchema',
},
}, },
}, },
'components/schemas': { 'components/schemas': {
featureSchema: { schema: featureSchema }, featureSchema,
constraintSchema,
featureEnvironmentSchema,
featureStrategySchema,
overrideSchema,
parametersSchema,
strategySchema,
variantSchema,
}, },
} as const; } as const;

View File

@ -5,7 +5,7 @@ import { parametersSchema } from './parameters-schema';
export const strategySchemaDefinition = { export const strategySchemaDefinition = {
type: 'object', type: 'object',
additionalProperties: false, additionalProperties: false,
required: ['name', 'constraints', 'parameters'], required: ['name'],
properties: { properties: {
id: { id: {
type: 'string', type: 'string',
@ -18,9 +18,13 @@ export const strategySchemaDefinition = {
}, },
constraints: { constraints: {
type: 'array', type: 'array',
items: { $ref: '#/components/schemas/constraintSchema' }, items: {
$ref: '#/components/schemas/constraintSchema',
},
},
parameters: {
$ref: '#/components/schemas/parametersSchema',
}, },
parameters: { $ref: '#/components/schemas/parametersSchema' },
}, },
'components/schemas': { 'components/schemas': {
constraintSchema, constraintSchema,

View File

@ -29,7 +29,9 @@ const schema = {
}, },
constraints: { constraints: {
type: 'array', type: 'array',
items: { $ref: '#/components/schemas/constraintSchema' }, items: {
$ref: '#/components/schemas/constraintSchema',
},
}, },
}, },
'components/schemas': { 'components/schemas': {

View File

@ -32,7 +32,9 @@ const schema = {
}, },
overrides: { overrides: {
type: 'array', type: 'array',
items: { $ref: '#/components/schemas/overrideSchema' }, items: {
$ref: '#/components/schemas/overrideSchema',
},
}, },
}, },
'components/schemas': { 'components/schemas': {

View File

@ -28,7 +28,6 @@ export type CreateSchemaType<T> = FromSchema<
// Create an OpenAPIV3.SchemaObject from a const schema object. // Create an OpenAPIV3.SchemaObject from a const schema object.
// Make sure the schema contains an object of refs for type generation. // Make sure the schema contains an object of refs for type generation.
// Pass an empty 'components/schemas' object if there are no refs in the schema. // Pass an empty 'components/schemas' object if there are no refs in the schema.
// Note: The order of the refs must match the order they are present in the object
export const createSchemaObject = < export const createSchemaObject = <
T extends { 'components/schemas': { [key: string]: object } }, T extends { 'components/schemas': { [key: string]: object } },
>( >(

View File

@ -24,7 +24,7 @@ import { FeatureSchema } from '../../../openapi/spec/feature-schema';
import { createStrategyRequest } from '../../../openapi/spec/create-strategy-request'; import { createStrategyRequest } from '../../../openapi/spec/create-strategy-request';
import { StrategySchema } from '../../../openapi/spec/strategy-schema'; import { StrategySchema } from '../../../openapi/spec/strategy-schema';
import { featuresResponse } from '../../../openapi/spec/features-response'; import { featuresResponse } from '../../../openapi/spec/features-response';
import { featureEnvironmentInfoResponse } from '../../../openapi/spec/feature-environment-info-response'; import { featureEnvironmentResponse } from '../../../openapi/spec/feature-environment-response';
import { strategiesResponse } from '../../../openapi/spec/strategies-response'; import { strategiesResponse } from '../../../openapi/spec/strategies-response';
import { strategyResponse } from '../../../openapi/spec/strategy-response'; import { strategyResponse } from '../../../openapi/spec/strategy-response';
import { emptyResponse } from '../../../openapi/spec/empty-response'; import { emptyResponse } from '../../../openapi/spec/empty-response';
@ -32,16 +32,12 @@ import { updateFeatureRequest } from '../../../openapi/spec/update-feature-reque
import { patchRequest } from '../../../openapi/spec/patch-request'; import { patchRequest } from '../../../openapi/spec/patch-request';
import { updateStrategyRequest } from '../../../openapi/spec/update-strategy-request'; import { updateStrategyRequest } from '../../../openapi/spec/update-strategy-request';
import { cloneFeatureRequest } from '../../../openapi/spec/clone-feature-request'; import { cloneFeatureRequest } from '../../../openapi/spec/clone-feature-request';
import { FeatureEnvironmentInfoSchema } from '../../../openapi/spec/feature-environment-info-schema'; import { FeatureEnvironmentSchema } from '../../../openapi/spec/feature-environment-schema';
import { ParametersSchema } from '../../../openapi/spec/parameters-schema'; import { ParametersSchema } from '../../../openapi/spec/parameters-schema';
import { FeaturesSchema } from '../../../openapi/spec/features-schema'; import { FeaturesSchema } from '../../../openapi/spec/features-schema';
import { UpdateFeatureSchema } from '../../../openapi/spec/updateFeatureSchema'; import { UpdateFeatureSchema } from '../../../openapi/spec/updateFeatureSchema';
import { UpdateStrategySchema } from '../../../openapi/spec/update-strategy-schema'; import { UpdateStrategySchema } from '../../../openapi/spec/update-strategy-schema';
import { CreateStrategySchema } from '../../../openapi/spec/create-strategy-schema'; import { CreateStrategySchema } from '../../../openapi/spec/create-strategy-schema';
import {
EnvironmentInfoMapper,
StrategyMapper,
} from '../../../openapi/mappers';
interface FeatureStrategyParams { interface FeatureStrategyParams {
projectId: string; projectId: string;
@ -77,11 +73,6 @@ type ProjectFeaturesServices = Pick<
export default class ProjectFeaturesController extends Controller { export default class ProjectFeaturesController extends Controller {
private featureService: FeatureToggleService; private featureService: FeatureToggleService;
private strategyMapper: StrategyMapper = new StrategyMapper();
private environmentMapper: EnvironmentInfoMapper =
new EnvironmentInfoMapper();
private readonly logger: Logger; private readonly logger: Logger;
constructor( constructor(
@ -102,7 +93,7 @@ export default class ProjectFeaturesController extends Controller {
openApiService.validPath({ openApiService.validPath({
tags: ['admin'], tags: ['admin'],
operationId: 'getEnvironment', operationId: 'getEnvironment',
responses: { 200: featureEnvironmentInfoResponse }, responses: { 200: featureEnvironmentResponse },
}), }),
], ],
}); });
@ -448,7 +439,7 @@ export default class ProjectFeaturesController extends Controller {
async getEnvironment( async getEnvironment(
req: Request<FeatureStrategyParams, any, any, any>, req: Request<FeatureStrategyParams, any, any, any>,
res: Response<FeatureEnvironmentInfoSchema>, res: Response<FeatureEnvironmentSchema>,
): Promise<void> { ): Promise<void> {
const { environment, featureName, projectId } = req.params; const { environment, featureName, projectId } = req.params;
const environmentInfo = await this.featureService.getEnvironmentInfo( const environmentInfo = await this.featureService.getEnvironmentInfo(
@ -456,7 +447,7 @@ export default class ProjectFeaturesController extends Controller {
environment, environment,
featureName, featureName,
); );
res.status(200).json(this.environmentMapper.toPublic(environmentInfo)); res.status(200).json(environmentInfo);
} }
async toggleEnvironmentOn( async toggleEnvironmentOn(
@ -496,11 +487,11 @@ export default class ProjectFeaturesController extends Controller {
const { projectId, featureName, environment } = req.params; const { projectId, featureName, environment } = req.params;
const userName = extractUsername(req); const userName = extractUsername(req);
const strategy = await this.featureService.createStrategy( const strategy = await this.featureService.createStrategy(
this.strategyMapper.mapInput(req.body), req.body,
{ environment, projectId, featureName }, { environment, projectId, featureName },
userName, userName,
); );
res.status(200).json(this.strategyMapper.toPublic(strategy)); res.status(200).json(strategy);
} }
async getStrategies( async getStrategies(
@ -514,9 +505,7 @@ export default class ProjectFeaturesController extends Controller {
featureName, featureName,
environment, environment,
); );
res.status(200).json( res.status(200).json(featureStrategies);
featureStrategies.map(this.strategyMapper.toPublic),
);
} }
async updateStrategy( async updateStrategy(
@ -531,7 +520,7 @@ export default class ProjectFeaturesController extends Controller {
{ environment, projectId, featureName }, { environment, projectId, featureName },
userName, userName,
); );
res.status(200).json(this.strategyMapper.fromPublic(updatedStrategy)); res.status(200).json(updatedStrategy);
} }
async patchStrategy( async patchStrategy(
@ -549,7 +538,7 @@ export default class ProjectFeaturesController extends Controller {
{ environment, projectId, featureName }, { environment, projectId, featureName },
userName, userName,
); );
res.status(200).json(this.strategyMapper.toPublic(updatedStrategy)); res.status(200).json(updatedStrategy);
} }
async getStrategy( async getStrategy(
@ -560,7 +549,7 @@ export default class ProjectFeaturesController extends Controller {
const { strategyId } = req.params; const { strategyId } = req.params;
this.logger.info(strategyId); this.logger.info(strategyId);
const strategy = await this.featureService.getStrategy(strategyId); const strategy = await this.featureService.getStrategy(strategyId);
res.status(200).json(this.strategyMapper.toPublic(strategy)); res.status(200).json(strategy);
} }
async deleteStrategy( async deleteStrategy(
@ -601,7 +590,7 @@ export default class ProjectFeaturesController extends Controller {
{ environment, projectId, featureName }, { environment, projectId, featureName },
userName, userName,
); );
res.status(200).json(this.strategyMapper.toPublic(updatedStrategy)); res.status(200).json(updatedStrategy);
} }
async getStrategyParameters( async getStrategyParameters(

View File

@ -22,8 +22,8 @@ export enum WeightType {
export interface IStrategyConfig { export interface IStrategyConfig {
id?: string; id?: string;
name: string; name: string;
constraints: IConstraint[]; constraints?: IConstraint[];
parameters: { [key: string]: string }; parameters?: { [key: string]: string };
sortOrder?: number; sortOrder?: number;
} }
export interface IFeatureStrategy { export interface IFeatureStrategy {

View File

@ -152,13 +152,16 @@ Object {
"type": "number", "type": "number",
}, },
}, },
"required": Array [
"name",
],
"type": "object", "type": "object",
}, },
"emptyResponseSchema": Object { "emptyResponseSchema": Object {
"description": "OK", "description": "OK",
"type": "object", "type": "object",
}, },
"featureEnvironmentInfoSchema": Object { "featureEnvironmentSchema": Object {
"additionalProperties": false, "additionalProperties": false,
"properties": Object { "properties": Object {
"enabled": Object { "enabled": Object {
@ -182,9 +185,7 @@ Object {
}, },
"required": Array [ "required": Array [
"name", "name",
"environment",
"enabled", "enabled",
"strategies",
], ],
"type": "object", "type": "object",
}, },
@ -207,7 +208,7 @@ Object {
}, },
"environments": Object { "environments": Object {
"items": Object { "items": Object {
"$ref": "#/components/schemas/featureEnvironmentInfoSchema", "$ref": "#/components/schemas/featureEnvironmentSchema",
}, },
"type": "array", "type": "array",
}, },
@ -246,7 +247,6 @@ Object {
}, },
"required": Array [ "required": Array [
"name", "name",
"project",
], ],
"type": "object", "type": "object",
}, },
@ -291,7 +291,6 @@ Object {
}, },
"required": Array [ "required": Array [
"id", "id",
"name",
"featureName", "featureName",
"strategyName", "strategyName",
"constraints", "constraints",
@ -394,8 +393,6 @@ Object {
}, },
"required": Array [ "required": Array [
"name", "name",
"constraints",
"parameters",
], ],
"type": "object", "type": "object",
}, },
@ -1078,11 +1075,11 @@ Object {
"content": Object { "content": Object {
"application/json": Object { "application/json": Object {
"schema": Object { "schema": Object {
"$ref": "#/components/schemas/featureEnvironmentInfoSchema", "$ref": "#/components/schemas/featureEnvironmentSchema",
}, },
}, },
}, },
"description": "featureEnvironmentInfoResponse", "description": "featureEnvironmentResponse",
}, },
}, },
"tags": Array [ "tags": Array [