UNPKG

@loopback/openapi-v3

Version:

Decorators that annotate LoopBack artifacts with OpenAPI v3 metadata and utilities that transform LoopBack metadata to OpenAPI v3 specifications

506 lines (487 loc) 16.7 kB
// 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 {FilterSchemaOptions, Model} from '@loopback/repository-json-schema'; import {getFilterSchemaFor, getWhereSchemaFor} from '../filter-schema'; import {resolveSchema} from '../generate-schema'; import {OAI3Keys} from '../keys'; import { isSchemaObject, ParameterLocation, ParameterObject, ReferenceObject, SchemaObject, } from '../types'; export const PARAMETER_INDEX = 'x-parameter-index'; /** * Describe an input parameter of a Controller method. * * `@param` must be applied to parameters. * * @example * ```ts * class MyController { * @get('/') * list( * @param(offsetSpec) offset?: number, * @param(pageSizeSpec) pageSize?: number, * ) {} * } * ``` * * @param paramSpec - Parameter specification. */ export function param(paramSpec: ParameterObject) { return function (target: object, member: string, index: number) { paramSpec = {...paramSpec}; // Get the design time method parameter metadata const methodSig = MetadataInspector.getDesignTypeForMethod(target, member); const paramTypes = methodSig?.parameterTypes || []; // Map design-time parameter type to the OpenAPI param type const paramType = paramTypes[index]; if (paramType) { if ( // generate schema if `paramSpec` doesn't have it !paramSpec.schema || // generate schema if `paramSpec` has `schema` but without `type` (isSchemaObject(paramSpec.schema) && !paramSpec.schema.type) ) { // If content explicitly mentioned do not resolve schema if (!paramSpec.content) { // please note `resolveSchema` only adds `type` and `format` for `schema` paramSpec.schema = resolveSchema(paramType, paramSpec.schema); } } } if ( paramSpec.schema && isSchemaObject(paramSpec.schema) && paramSpec.schema.type === 'array' ) { // The design-time type is `Object` for `any` if (paramType != null && paramType !== Object && paramType !== Array) { throw new Error( `The parameter type is set to 'array' but the JavaScript type is ${paramType.name}`, ); } } ParameterDecoratorFactory.createDecorator<ParameterObject>( OAI3Keys.PARAMETERS_KEY, paramSpec, {decoratorName: '@param'}, )(target, member, index); }; } /** * The `type` and `format` inferred by a common name of OpenAPI 3.0.0 data type * reference link: * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#data-types */ const builtinTypes: Record<string, ParamShortcutOptions> = { string: {type: 'string'}, boolean: {type: 'boolean'}, number: {type: 'number'}, integer: {type: 'integer', format: 'int32'}, long: {type: 'integer', format: 'int64'}, float: {type: 'number', format: 'float'}, double: {type: 'number', format: 'double'}, byte: {type: 'string', format: 'byte'}, binary: {type: 'string', format: 'binary'}, date: {type: 'string', format: 'date'}, dateTime: {type: 'string', format: 'date-time'}, password: {type: 'string', format: 'password'}, }; /** * Namespace for `@param.*` decorators */ export namespace param { /** * Query parameter decorator */ export const query = { /** * Define a parameter of "integer" type that's read from the query string. * Usage: ` @param.query.string('paramName')` * * @param name - Parameter name. */ string: createParamShortcut('query', builtinTypes.string), /** * Define a parameter of "number" type that's read from the query string. * Usage: ` @param.query.number('paramName')` * * @param name - Parameter name. */ number: createParamShortcut('query', builtinTypes.number), /** * Define a parameter of "boolean" type that's read from the query string. * Usage: ` @param.query.boolean('paramName')` * * @param name - Parameter name. */ boolean: createParamShortcut('query', builtinTypes.boolean), /** * Define a parameter of "integer" type that's read from the query string. * Usage: ` @param.query.integer('paramName')` * * @param name - Parameter name. */ integer: createParamShortcut('query', builtinTypes.integer), /** * Define a parameter of "long" type that's read from the query string. * Usage: ` @param.query.long('paramName')` * * @param name - Parameter name. */ long: createParamShortcut('query', builtinTypes.long), /** * Define a parameter of "float" type that's read from the query string. * Usage: ` @param.query.float('paramName')` * * @param name - Parameter name. */ float: createParamShortcut('query', builtinTypes.float), /** * Define a parameter of "double" type that's read from the query string. * Usage: ` @param.query.double('paramName')` * * @param name - Parameter name. */ double: createParamShortcut('query', builtinTypes.double), /** * Define a parameter of "byte" type that's read from the query string. * Usage: ` @param.query.byte('paramName')` * * @param name - Parameter name. */ byte: createParamShortcut('query', builtinTypes.byte), /** * Define a parameter of "binary" type that's read from the query string. * Usage: ` @param.query.binary('paramName')` * * @param name - Parameter name. */ binary: createParamShortcut('query', builtinTypes.binary), /** * Define a parameter of "date" type that's read from the query string. * Usage: ` @param.query.date('paramName')` * * @param name - Parameter name. */ date: createParamShortcut('query', builtinTypes.date), /** * Define a parameter of "dateTime" type that's read from the query string. * Usage: ` @param.query.dateTime('paramName')` * * @param name - Parameter name. */ dateTime: createParamShortcut('query', builtinTypes.dateTime), /** * Define a parameter of "password" type that's read from the query string. * Usage: ` @param.query.password('paramName')` * * @param name - Parameter name. */ password: createParamShortcut('query', builtinTypes.password), /** * Define a parameter accepting an object value encoded * - as a JSON string, e.g. `filter={"where":{"id":1}}`); or * - in multiple nested keys, e.g. `filter[where][id]=1` * * @param name - Parameter name * @param schema - Optional OpenAPI Schema describing the object value. */ object: function ( name: string, schema: SchemaObject | ReferenceObject = { type: 'object', additionalProperties: true, }, spec?: Partial<ParameterObject>, ) { schema = { type: 'object', ...schema, }; return param({ name, in: 'query', content: { 'application/json': { schema, }, }, ...spec, }); }, }; /** * Header parameter decorator */ export const header = { /** * Define a parameter of "string" type that's read from a request header. * Usage: ` @param.header.string('paramName')` * * @param name - Parameter name, it must match the header name * (e.g. `Content-Type`). */ string: createParamShortcut('header', builtinTypes.string), /** * Define a parameter of "number" type that's read from a request header. * Usage: ` @param.header.number('paramName')` * * @param name - Parameter name, it must match the header name * (e.g. `Content-Length`). */ number: createParamShortcut('header', builtinTypes.number), /** * Define a parameter of "boolean" type that's read from a request header. * Usage: ` @param.header.boolean('paramName')` * * @param name - Parameter name, it must match the header name * (e.g. `DNT` or `X-Do-Not-Track`). */ boolean: createParamShortcut('header', builtinTypes.boolean), /** * Define a parameter of "integer" type that's read from a request header. * Usage: ` @param.header.integer('paramName')` * * @param name - Parameter name, it must match the header name * (e.g. `Content-Length`). */ integer: createParamShortcut('header', builtinTypes.integer), /** * Define a parameter of "long" type that's read from a request header. * Usage: ` @param.header.long('paramName')` * * @param name - Parameter name, it must match the header name */ long: createParamShortcut('header', builtinTypes.long), /** * Define a parameter of "float" type that's read from a request header. * Usage: ` @param.header.float('paramName')` * * @param name - Parameter name, it must match the header name */ float: createParamShortcut('header', builtinTypes.float), /** * Define a parameter of "double" type that's read from a request header. * Usage: ` @param.header.double('paramName')` * * @param name - Parameter name, it must match the header name */ double: createParamShortcut('header', builtinTypes.double), /** * Define a parameter of "byte" type that's read from a request header. * Usage: ` @param.header.byte('paramName')` * * @param name - Parameter name, it must match the header name */ byte: createParamShortcut('header', builtinTypes.byte), /** * Define a parameter of "binary" type that's read from a request header. * Usage: ` @param.header.binary('paramName')` * * @param name - Parameter name, it must match the header name */ binary: createParamShortcut('header', builtinTypes.binary), /** * Define a parameter of "date" type that's read from a request header. * Usage: ` @param.header.date('paramName')` * * @param name - Parameter name, it must match the header name */ date: createParamShortcut('header', builtinTypes.date), /** * Define a parameter of "dateTime" type that's read from a request header. * Usage: ` @param.header.dateTime('paramName')` * * @param name - Parameter name, it must match the header name */ dateTime: createParamShortcut('header', builtinTypes.dateTime), /** * Define a parameter of "password" type that's read from a request header. * Usage: ` @param.header.password('paramName')` * * @param name - Parameter name, it must match the header name */ password: createParamShortcut('header', builtinTypes.password), }; /** * Path parameter decorator */ export const path = { /** * Define a parameter of "string" type that's read from request path. * Usage: ` @param.path.string('paramName')` * * @param name - Parameter name matching one of the placeholders in the path */ string: createParamShortcut('path', builtinTypes.string), /** * Define a parameter of "number" type that's read from request path. * Usage: ` @param.path.number('paramName')` * * @param name - Parameter name matching one of the placeholders in the path */ number: createParamShortcut('path', builtinTypes.number), /** * Define a parameter of "boolean" type that's read from request path. * Usage: ` @param.path.boolean('paramName')` * * @param name - Parameter name matching one of the placeholders in the path */ boolean: createParamShortcut('path', builtinTypes.boolean), /** * Define a parameter of "integer" type that's read from request path. * Usage: ` @param.path.integer('paramName')` * * @param name - Parameter name matching one of the placeholders in the path */ integer: createParamShortcut('path', builtinTypes.integer), /** * Define a parameter of "long" type that's read from request path. * Usage: ` @param.path.long('paramName')` * * @param name - Parameter name matching one of the placeholders in the path */ long: createParamShortcut('path', builtinTypes.long), /** * Define a parameter of "float" type that's read from request path. * Usage: ` @param.path.float('paramName')` * * @param name - Parameter name matching one of the placeholders in the path */ float: createParamShortcut('path', builtinTypes.float), /** * Define a parameter of "double" type that's read from request path. * Usage: ` @param.path.double('paramName')` * * @param name - Parameter name matching one of the placeholders in the path */ double: createParamShortcut('path', builtinTypes.double), /** * Define a parameter of "byte" type that's read from request path. * Usage: ` @param.path.byte('paramName')` * * @param name - Parameter name matching one of the placeholders in the path */ byte: createParamShortcut('path', builtinTypes.byte), /** * Define a parameter of "binary" type that's read from request path. * Usage: ` @param.path.binary('paramName')` * * @param name - Parameter name matching one of the placeholders in the path */ binary: createParamShortcut('path', builtinTypes.binary), /** * Define a parameter of "date" type that's read from request path. * Usage: ` @param.path.date('paramName')` * * @param name - Parameter name matching one of the placeholders in the path */ date: createParamShortcut('path', builtinTypes.date), /** * Define a parameter of "dateTime" type that's read from request path. * Usage: ` @param.path.dateTime('paramName')` * * @param name - Parameter name matching one of the placeholders in the path */ dateTime: createParamShortcut('path', builtinTypes.dateTime), /** * Define a parameter of "password" type that's read from request path. * Usage: ` @param.path.password('paramName')` * * @param name - Parameter name matching one of the placeholders in the path */ password: createParamShortcut('path', builtinTypes.password), }; /** * Define a parameter of `array` type. * * @example * ```ts * export class MyController { * @get('/greet') * greet(@param.array('names', 'query', {type: 'string'}) names: string[]): string { * return `Hello, ${names}`; * } * } * ``` * * @param name - Parameter name * @param source - Source of the parameter value * @param itemSpec - Item type for the array or the full item object */ export const array = function ( name: string, source: ParameterLocation, itemSpec: SchemaObject | ReferenceObject, ) { return param({ name, in: source, schema: {type: 'array', items: itemSpec}, }); }; /** * Sugar decorator for `filter` query parameter * * @example * ```ts * async find( * @param.filter(modelCtor)) filter?: Filter<T>, * ): Promise<(T & Relations)[]> { * // ... * } * ``` * @param modelCtor - Model class * @param options - Options to customize the parameter name or filter schema * */ export function filter( modelCtor: typeof Model, options?: string | (FilterSchemaOptions & {name?: string}), ) { let name = 'filter'; if (typeof options === 'string') { name = options; options = {}; } name = options?.name ?? name; return param.query.object(name, getFilterSchemaFor(modelCtor, options)); } /** * Sugar decorator for `where` query parameter * * @example * ```ts * async count( * @param.where(modelCtor)) where?: Where<T>, * ): Promise<Count> { * // ... * } * ``` * @param modelCtor - Model class * @param name - Custom name for the parameter, default to `where` * */ export function where(modelCtor: typeof Model, name = 'where') { return param.query.object(name, getWhereSchemaFor(modelCtor)); } } interface ParamShortcutOptions { type: SchemaObject['type']; format?: string; } function createParamShortcut( source: ParameterLocation, options: ParamShortcutOptions, ) { return (name: string, spec?: Partial<ParameterObject>) => { return param({name, in: source, schema: {...options}, ...spec}); }; }