1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-05-17 01:17:29 +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 { featureSchema } from './spec/feature-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 { patchOperationSchema } from './spec/patch-operation-schema';
import { updateFeatureSchema } from './spec/updateFeatureSchema';
@ -48,7 +48,7 @@ export const createOpenApiSchema = (
createStrategySchema,
featureSchema,
featuresSchema,
featureEnvironmentInfoSchema,
featureEnvironmentSchema,
featureStrategySchema,
emptyResponseSchema,
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 = {
type: 'object',
additionalProperties: false,
required: ['name'],
properties: {
name: {
type: 'string',
@ -14,7 +15,9 @@ const schema = {
},
constraints: {
type: 'array',
items: { $ref: '#/components/schemas/constraintSchema' },
items: {
$ref: '#/components/schemas/constraintSchema',
},
},
parameters: {
$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 { featureStrategySchema } from './feature-strategy-schema';
import { constraintSchema } from './constraint-schema';
import { parametersSchema } from './parameters-schema';
let schema = {
type: 'object',
additionalProperties: false,
required: ['name', 'environment', 'enabled', 'strategies'],
required: ['name', 'enabled'],
properties: {
name: {
type: 'string',
@ -27,9 +29,11 @@ let schema = {
},
'components/schemas': {
featureStrategySchema,
constraintSchema,
parametersSchema,
},
} 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 { strategySchema } from './strategy-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 = {
type: 'object',
additionalProperties: false,
required: ['name', 'project'],
required: ['name'],
properties: {
name: {
type: 'string',
@ -45,12 +49,14 @@ const schema = {
environments: {
type: 'array',
items: {
$ref: '#/components/schemas/featureEnvironmentInfoSchema',
$ref: '#/components/schemas/featureEnvironmentSchema',
},
},
strategies: {
type: 'array',
items: { $ref: '#/components/schemas/strategySchema' },
items: {
$ref: '#/components/schemas/strategySchema',
},
},
variants: {
type: 'array',
@ -60,7 +66,11 @@ const schema = {
},
},
'components/schemas': {
featureEnvironmentInfoSchema,
constraintSchema,
featureEnvironmentSchema,
featureStrategySchema,
overrideSchema,
parametersSchema,
strategySchema,
variantSchema,
},

View File

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

View File

@ -1,5 +1,12 @@
import { createSchemaObject, CreateSchemaType } from '../types';
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 = {
type: 'object',
@ -11,11 +18,20 @@ const schema = {
},
features: {
type: 'array',
items: { $ref: '#/components/schemas/featureSchema' },
items: {
$ref: '#/components/schemas/featureSchema',
},
},
},
'components/schemas': {
featureSchema: { schema: featureSchema },
featureSchema,
constraintSchema,
featureEnvironmentSchema,
featureStrategySchema,
overrideSchema,
parametersSchema,
strategySchema,
variantSchema,
},
} as const;

View File

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

View File

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

View File

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

View File

@ -28,7 +28,6 @@ export type CreateSchemaType<T> = FromSchema<
// Create an OpenAPIV3.SchemaObject from a const schema object.
// 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.
// Note: The order of the refs must match the order they are present in the object
export const createSchemaObject = <
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 { StrategySchema } from '../../../openapi/spec/strategy-schema';
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 { strategyResponse } from '../../../openapi/spec/strategy-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 { updateStrategyRequest } from '../../../openapi/spec/update-strategy-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 { FeaturesSchema } from '../../../openapi/spec/features-schema';
import { UpdateFeatureSchema } from '../../../openapi/spec/updateFeatureSchema';
import { UpdateStrategySchema } from '../../../openapi/spec/update-strategy-schema';
import { CreateStrategySchema } from '../../../openapi/spec/create-strategy-schema';
import {
EnvironmentInfoMapper,
StrategyMapper,
} from '../../../openapi/mappers';
interface FeatureStrategyParams {
projectId: string;
@ -77,11 +73,6 @@ type ProjectFeaturesServices = Pick<
export default class ProjectFeaturesController extends Controller {
private featureService: FeatureToggleService;
private strategyMapper: StrategyMapper = new StrategyMapper();
private environmentMapper: EnvironmentInfoMapper =
new EnvironmentInfoMapper();
private readonly logger: Logger;
constructor(
@ -102,7 +93,7 @@ export default class ProjectFeaturesController extends Controller {
openApiService.validPath({
tags: ['admin'],
operationId: 'getEnvironment',
responses: { 200: featureEnvironmentInfoResponse },
responses: { 200: featureEnvironmentResponse },
}),
],
});
@ -448,7 +439,7 @@ export default class ProjectFeaturesController extends Controller {
async getEnvironment(
req: Request<FeatureStrategyParams, any, any, any>,
res: Response<FeatureEnvironmentInfoSchema>,
res: Response<FeatureEnvironmentSchema>,
): Promise<void> {
const { environment, featureName, projectId } = req.params;
const environmentInfo = await this.featureService.getEnvironmentInfo(
@ -456,7 +447,7 @@ export default class ProjectFeaturesController extends Controller {
environment,
featureName,
);
res.status(200).json(this.environmentMapper.toPublic(environmentInfo));
res.status(200).json(environmentInfo);
}
async toggleEnvironmentOn(
@ -496,11 +487,11 @@ export default class ProjectFeaturesController extends Controller {
const { projectId, featureName, environment } = req.params;
const userName = extractUsername(req);
const strategy = await this.featureService.createStrategy(
this.strategyMapper.mapInput(req.body),
req.body,
{ environment, projectId, featureName },
userName,
);
res.status(200).json(this.strategyMapper.toPublic(strategy));
res.status(200).json(strategy);
}
async getStrategies(
@ -514,9 +505,7 @@ export default class ProjectFeaturesController extends Controller {
featureName,
environment,
);
res.status(200).json(
featureStrategies.map(this.strategyMapper.toPublic),
);
res.status(200).json(featureStrategies);
}
async updateStrategy(
@ -531,7 +520,7 @@ export default class ProjectFeaturesController extends Controller {
{ environment, projectId, featureName },
userName,
);
res.status(200).json(this.strategyMapper.fromPublic(updatedStrategy));
res.status(200).json(updatedStrategy);
}
async patchStrategy(
@ -549,7 +538,7 @@ export default class ProjectFeaturesController extends Controller {
{ environment, projectId, featureName },
userName,
);
res.status(200).json(this.strategyMapper.toPublic(updatedStrategy));
res.status(200).json(updatedStrategy);
}
async getStrategy(
@ -560,7 +549,7 @@ export default class ProjectFeaturesController extends Controller {
const { strategyId } = req.params;
this.logger.info(strategyId);
const strategy = await this.featureService.getStrategy(strategyId);
res.status(200).json(this.strategyMapper.toPublic(strategy));
res.status(200).json(strategy);
}
async deleteStrategy(
@ -601,7 +590,7 @@ export default class ProjectFeaturesController extends Controller {
{ environment, projectId, featureName },
userName,
);
res.status(200).json(this.strategyMapper.toPublic(updatedStrategy));
res.status(200).json(updatedStrategy);
}
async getStrategyParameters(

View File

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

View File

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