@loopback/openapi-v3
Version:
Decorators that annotate LoopBack artifacts with OpenAPI v3 metadata and utilities that transform LoopBack metadata to OpenAPI v3 specifications
218 lines (207 loc) • 6.32 kB
text/typescript
// Copyright IBM Corp. and LoopBack contributors 2018,2020. All Rights Reserved.
// Node module: @loopback/openapi-v3
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
import {MetadataInspector, ParameterDecoratorFactory} from '@loopback/core';
import _ from 'lodash';
import {inspect} from 'util';
import {resolveSchema} from '../generate-schema';
import {OAI3Keys} from '../keys';
import {ReferenceObject, RequestBodyObject, SchemaObject} from '../types';
const debug = require('debug')('loopback:openapi3:metadata:requestbody');
export const REQUEST_BODY_INDEX = 'x-parameter-index';
/**
* Describe the request body of a Controller method parameter.
*
* A typical OpenAPI requestBody spec contains property:
* - `description`
* - `required`
* - `content`.
*
* @example
* ```ts
* requestBodySpec: {
* description: 'a user',
* required: true,
* content: {
* 'application/json': {...schemaSpec},
* 'application/text': {...schemaSpec},
* },
* }
* ```
*
* If the `content` object is not provided, this decorator sets it
* as `application/json` by default.
* If the `schema` object is not provided in a media type, this decorator
* generates it for you based on the argument's type. In this case, please
* make sure the argument type is a class decorated by `@model` from
* `@loopback/repository`
*
* The simplest usage is:
*
* ```ts
* class MyController {
* @post('/User')
* async create(@requestBody() user: User) {}
* }
* ```
*
* or with properties other than `content`
*
* ```ts
* class MyController {
* @post('/User')
* async create(@requestBody({description: 'a user'}) user: User) {}
* }
* ```
*
* or to be more complicated, with your customized media type
*
* ```ts
* class MyController {
* @post('/User')
* async create(@requestBody({
* description: 'a user',
* // leave the schema as empty object, the decorator will generate it.
* content: {'application/text': {}}
* }) user: User) {}
* }
* ```
*
* @param requestBodySpec - The complete requestBody object or partial of it.
* "partial" for allowing no `content` in spec, for example:
* ```
* @requestBody({description: 'a request body'}) user: User
* ```
*/
export function requestBody(requestBodySpec?: Partial<RequestBodyObject>) {
return function (target: object, member: string, index: number) {
debug('@requestBody() on %s.%s', target.constructor.name, member);
debug(' parameter index: %s', index);
/* istanbul ignore if */
if (debug.enabled)
debug(' options: %s', inspect(requestBodySpec, {depth: null}));
// Use 'application/json' as default content if `requestBody` is undefined
requestBodySpec = {content: {}, ...requestBodySpec};
if (_.isEmpty(requestBodySpec.content))
requestBodySpec.content = {'application/json': {}};
// Get the design time method parameter metadata
const methodSig = MetadataInspector.getDesignTypeForMethod(target, member);
const paramTypes = methodSig?.parameterTypes || [];
const paramType = paramTypes[index];
const schema = resolveSchema(paramType);
/* istanbul ignore if */
if (debug.enabled)
debug(' inferred schema: %s', inspect(schema, {depth: null}));
requestBodySpec.content = _.mapValues(requestBodySpec.content, c => {
if (!c.schema) {
c.schema = schema;
}
return c;
});
/* istanbul ignore if */
if (debug.enabled)
debug(' final spec: ', inspect(requestBodySpec, {depth: null}));
ParameterDecoratorFactory.createDecorator<RequestBodyObject>(
OAI3Keys.REQUEST_BODY_KEY,
requestBodySpec as RequestBodyObject,
{decoratorName: '@requestBody'},
)(target, member, index);
};
}
export namespace requestBody {
/**
* Define a requestBody of `array` type.
*
* @example
* ```ts
* export class MyController {
* @post('/greet')
* greet(@requestBody.array(
* {type: 'string'},
* {description: 'an array of names', required: false}
* ) names: string[]): string {
* return `Hello, ${names}`;
* }
* }
* ```
*
* @param properties - The requestBody properties other than `content`
* @param itemSpec - the full item object
*/
export const array = (
itemSpec: SchemaObject | ReferenceObject,
properties?: {description?: string; required?: boolean},
) => {
return requestBody({
...properties,
content: {
'application/json': {
schema: {type: 'array', items: itemSpec},
},
},
});
};
/**
* Define a requestBody of `file` type. This is used to support
* multipart/form-data based file upload. Use `@requestBody` for other content
* types.
*
* {@link https://swagger.io/docs/specification/describing-request-body/file-upload | OpenAPI file upload}
*
* @example
* import {Request} from '@loopback/rest';
*
* ```ts
* class MyController {
* @post('/pictures')
* upload(
* @requestBody.file()
* request: Request,
* ) {
* // ...
* }
* }
* ```
*
* @param properties - Optional description and required flag
*/
export const file = (properties?: {
description?: string;
required?: boolean;
}) => {
return requestBody({
description: 'Request body for multipart/form-data based file upload',
required: true,
content: {
// Media type for file upload
'multipart/form-data': {
// Skip body parsing
'x-parser': 'stream',
schema: {
type: 'object',
properties: {
file: {
type: 'string',
// This is required by OpenAPI spec 3.x for file upload
format: 'binary',
},
// Multiple file upload is not working with swagger-ui
// https://github.com/swagger-api/swagger-ui/issues/4600
/*
files: {
type: 'array',
items: {
type: 'string',
format: 'binary',
},
},
*/
},
},
},
},
...properties,
});
};
}