1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-06 00:07:44 +01:00
unleash.unleash/src/lib/services/openapi-service.ts
Thomas Heartman 78cf9d03aa
refactor: switch to upstream express-openapi (#5259)
Switch the express-openapi implementation from our internal fork to the
upstream version. We have upstreamed our changes and a new version has
been released, so this should be the last step before we can retire our
fork.

Because some of the dependencies have been updated since our internal
fork, we also need to update some of our error handling to reflect this.
2023-11-06 08:22:02 +01:00

104 lines
2.9 KiB
TypeScript

import openapi, { IExpressOpenApi } from '@wesleytodd/openapi';
import { Express, RequestHandler, Response } from 'express';
import { IUnleashConfig } from '../types/option';
import {
createOpenApiSchema,
JsonSchemaProps,
removeJsonSchemaProps,
SchemaId,
} from '../openapi';
import { ApiOperation } from '../openapi/util/api-operation';
import { Logger } from '../logger';
import { validateSchema } from '../openapi/validate';
import { IFlagResolver } from '../types';
import { fromOpenApiValidationErrors } from '../error/bad-data-error';
export class OpenApiService {
private readonly config: IUnleashConfig;
private readonly logger: Logger;
private readonly api: IExpressOpenApi;
private flagResolver: IFlagResolver;
constructor(config: IUnleashConfig) {
this.config = config;
this.flagResolver = config.flagResolver;
this.logger = config.getLogger('openapi-service.ts');
this.api = openapi(
this.docsPath(),
createOpenApiSchema(config.server),
{
coerce: true,
extendRefs: true,
basePath: config.server.baseUriPath,
},
);
}
validPath(op: ApiOperation): RequestHandler {
return this.api.validPath(op);
}
useDocs(app: Express): void {
app.use(this.api);
app.use(this.docsPath(), this.api.swaggerui);
}
docsPath(): string {
const { baseUriPath = '' } = this.config.server ?? {};
return `${baseUriPath}/docs/openapi`;
}
registerCustomSchemas<T extends JsonSchemaProps>(
schemas: Record<string, T>,
): void {
Object.entries(schemas).forEach(([name, schema]) => {
this.api.schema(name, removeJsonSchemaProps(schema));
});
}
useErrorHandler(app: Express): void {
app.use((err, req, res, next) => {
if (err?.status && err.validationErrors) {
const apiError = fromOpenApiValidationErrors(
req.body,
err.validationErrors,
);
res.status(apiError.statusCode).json(apiError);
} else {
next(err);
}
});
}
respondWithValidation<T, S = SchemaId>(
status: number,
res: Response<T>,
schema: S,
data: T,
headers: { [header: string]: string } = {},
): void {
const errors = validateSchema<S>(schema, data);
if (errors) {
this.logger.debug(
'Invalid response:',
JSON.stringify(errors, null, 4),
);
if (this.flagResolver.isEnabled('strictSchemaValidation')) {
throw new Error(JSON.stringify(errors, null, 4));
}
}
Object.entries(headers).forEach(([header, value]) =>
res.header(header, value),
);
res.status(status).json(data);
}
}