@ditsmod/openapi
Щоб створити документацію за специфікацією OpenAPI, можна використовувати модуль @ditsmod/openapi
.
Встановлення
npm i @ditsmod/openapi
Створення документації
Щоб створювати окремі маршрути, користуйтесь декоратором oasRoute
, в якому четвертим або третім параметром (якщо немає ґардів) йде так званий Operation Object:
import { controller } from '@ditsmod/core';
import { oasRoute } from '@ditsmod/openapi';
@controller()
export class SomeController {
// ...
@oasRoute('GET', 'users/:username', {
parameters: [
{
name: 'username',
in: 'path',
required: true,
description: 'Username of the profile to get',
schema: {
type: 'string',
},
},
],
})
async getSome() {
// ...
}
}
Ditsmod має хорошу підтримку TypeScript-моделей для OpenAPI v3.1.0, зокрема й Operation Object, але вручну прописувати весь Operation Object прямо в коді до кожного роуту - не обов'язково. Краще скористатись хелперами, які за вас згенерують необхідний код, та ще й зменшать кількість помилок за рахунок ще кращої пітримки TypeScript. Ditsmod має декілька таких хелперів: getParams()
, getContent()
, Parameters
, Content
. Усі вони імпортуються з модуля @ditsmod/openapi
.
Передача параметрів Operation Object
В наступному прикладі за допомогою хелпера getParams()
запи сано майже усе те, що у попередньому прикладі ми прописали вручну для parameters
:
import { controller } from '@ditsmod/core';
import { oasRoute, getParams } from '@ditsmod/openapi';
@controller()
export class SomeController {
// ...
@oasRoute('GET', 'users/:username', {
parameters: getParams('path', true, 'username'),
})
async getSome() {
// ...
}
}
Тут не вистачає ще вказання типу даних для параметра username
та його опису. Рекомендуємо використовувати TypeScript-клас у якості моделі, щоб потім можна було на нього посилатись за допомогою хелперів, які вміють читати його метадані і повертати готові JSON-об'єкти.
Створення TypeScript-моделей
В наступному прикладі показано модель з трьома параметрами:
import { property } from '@ditsmod/openapi';
class Params {
@property({ description: 'Username of the profile to get.' })
username: string;
@property({ minimum: 1, maximum: 100, description: 'Page number.' })
page: number;
@property()
hasName: boolean;
}
Як бачите, щоб закріпити метадані за моделлю, використовується декоратор @property()
, куди ви можете передавати першим аргументом Schema Object.
Зверніть увагу, що в даному разі властивість type
не прописується у метаданих, оскільки указані тут типи автоматично читаються хелперами. Щоправда не усі наявні у TypeScript типи можуть читатись. Наприклад, хелпери не зможуть автоматично побачити який тип масиву ви передаєте. Це саме стосується enum
. Також хелпери не бачать чи є властивість об'єкта опціональною чи ні.
Тип масиву чи enum
можна передати другим параметром в декоратор @property()
:
import { property } from '@ditsmod/openapi';
enum NumberEnum {
one,
two,
three,
}
class Params {
@property({}, { enum: NumberEnum })
property1: NumberEnum;
@property({}, { array: String })
property2: string[];
@property({}, { array: [String, Number] })
property3: (string | number)[];
@property({}, { array: [[String]] }) // Масив в масиві
property4: string[][];
}
Посилання одних моделей на інші добре читаються. В наступному прикладі Model2
має посилання на Model1
:
import { property } from '@ditsmod/openapi';
export class Model1 {
@property()
property1: string;
}
export class Model2 {
@property()
model1: Model1;
@property({}, Model1)
arrModel1: Model1[];
}
Використання TypeScript-моделей
Хелпер getParams()
дозволяє використовувати моделі, і якщо ви зробите помилку у назві параметра, TypeScript скаже вам про це:
import { controller } from '@ditsmod/core';
import { oasRoute, getParams } from '@ditsmod/openapi';
import { Params } from './params.js';
@controller()
export class SomeController {
// ...
@oasRoute('GET', '', {
parameters: getParams('path', true, Params, 'username'),
})
async getSome() {
// ...
}
}
Тут Params
- це клас, який використовується у якості моделі параметрів.
Але хелпер getParams()
не призначений щоб його використовували одночасно для обов'язкових та необов'язкових параметрів. Також через нього не можна передавати опис параметрів, який відрізняється від опису параметрів у моделі параметрів. Для таких цілей можна скористатись іншим хелпером - Parameters
:
import { controller } from '@ditsmod/core';
import { oasRoute, Parameters } from '@ditsmod/openapi';
import { Params } from './params.js';
@controller()
export class SomeController {
// ...
@oasRoute('GET', '', {
parameters: new Parameters()
.required('path', Params, 'username')
.optional('query', Params, 'page', 'hasName')
.getParams(),
})
async getSome() {
// ...
}
}
requestBody та responses content
Моделі даних також використовуються щоб описати контент requestBody
, але тут є одна невелика відмінність у порівнянні з параметрами. По дефолту, усі властивості моделі є необов'язковими, і щоб позначити певну властивість обов'язковою, необхідно скористатись константою REQUIRED
:
import { property, REQUIRED } from '@ditsmod/openapi';
class Model1 {
@property()
property1: string;
@property({ [REQUIRED]: true })
property2: number;
}
Якщо дана модель буде використовуватись для опису requestBody
, то property2
в ній буде обов'язковою. Але якщо цю модель використовувати для опису параметрів, маркер REQUIRED
буде ігноруватись:
class SomeController {
// ...
@oasRoute('GET', 'users', {
parameters: getParams('query', false, Model1, 'property2'),
})
async getSome() {
// ...
}
}
Для опису контента в requestBody
та responses
існує також хелпер getContent()
:
import { controller, Status } from '@ditsmod/core';
import { oasRoute, getContent } from '@ditsmod/openapi';
import { SomeModel } from '#models/some.js';
@controller()
export class SomeController {
// ...
@oasRoute('POST', '', {
requestBody: {
description: 'All properties are taken from Model1.',
content: getContent({ mediaType: 'application/json', model: Model1 }),
},
})
async postSome() {
// ...
}
}
Хелпер getContent()
приймає скорочену версію даних, коли потрібно описати єдиний варіант mediaType
. Якщо ж вам потрібно описати більшу кількість mediaType
, можна скористатись класом Content
:
import { controller, Status } from '@ditsmod/core';
import { oasRoute, Content } from '@ditsmod/openapi';
import { SomeModel } from '#models/some.js';
@controller()
export class SomeController {
// ...
@oasRoute('GET', '', {
responses: {
[Status.OK]: {
description: 'Опис контенту із даним статусом',
content: new Content()
.set({ mediaType: 'application/xml', model: SomeModel })
.set({ mediaType: 'application/json', model: SomeModel })
.get(),
},
},
})
async getSome() {
// ...
}
}