diff --git a/src/services/MenuBuilder.ts b/src/services/MenuBuilder.ts index 8a45bb9b..7aacf198 100644 --- a/src/services/MenuBuilder.ts +++ b/src/services/MenuBuilder.ts @@ -6,6 +6,7 @@ import { Referenced, OpenAPIServer, OpenAPIPaths, + OpenAPIPath, } from '../types'; import { isOperationName, @@ -53,7 +54,7 @@ export class MenuBuilder { const spec = parser.spec; const items: ContentItemModel[] = []; - const tagsMap = MenuBuilder.getTagsWithOperations(spec); + const tagsMap = MenuBuilder.getTagsWithOperations(parser, spec); items.push(...MenuBuilder.addMarkdownItems(spec.info.description || '', undefined, 1, options)); if (spec['x-tagGroups'] && spec['x-tagGroups'].length > 0) { items.push( @@ -215,7 +216,7 @@ export class MenuBuilder { /** * collects tags and maps each tag to list of operations belonging to this tag */ - static getTagsWithOperations(spec: OpenAPISpec): TagsInfoMap { + static getTagsWithOperations(parser: OpenAPIParser, spec: OpenAPISpec): TagsInfoMap { const tags: TagsInfoMap = {}; const webhooks = spec['x-webhooks'] || spec.webhooks; for (const tag of spec.tags || []) { @@ -223,19 +224,26 @@ export class MenuBuilder { } if (webhooks) { - getTags(webhooks, true); + getTags(parser, webhooks, true); } if (spec.paths){ - getTags(spec.paths); + getTags(parser, spec.paths); } - function getTags(paths: OpenAPIPaths, isWebhook?: boolean) { + function getTags(parser: OpenAPIParser, paths: OpenAPIPaths, isWebhook?: boolean) { for (const pathName of Object.keys(paths)) { const path = paths[pathName]; - const operations = Object.keys(path).filter(isOperationName); - for (const operationName of operations) { - const operationInfo = path[operationName]; + const operations = Object.keys(path); + for (let operationName of operations) { + let operationInfo = isOperationName(operationName) && path[operationName]; + + if (!isOperationName(operationName) && path[operationName].$ref) { + const resolvedOperationInfo = parser.deref(path[operationName] || {}) + operationInfo = resolvedOperationInfo + delete operationInfo.put + operationName = resolvedOperationInfo[Object.keys(resolvedOperationInfo)[0]] + } let operationTags = operationInfo.tags; if (!operationTags || !operationTags.length) { diff --git a/src/services/SpecStore.ts b/src/services/SpecStore.ts index 1f39f903..277e2c03 100644 --- a/src/services/SpecStore.ts +++ b/src/services/SpecStore.ts @@ -1,4 +1,4 @@ -import { OpenAPIExternalDocumentation, OpenAPISpec } from '../types'; +import { OpenAPIExternalDocumentation, OpenAPIPath, OpenAPISpec, Referenced } from '../types'; import { ContentItemModel, MenuBuilder } from './MenuBuilder'; import { ApiInfoModel } from './models/ApiInfo'; @@ -28,6 +28,7 @@ export class SpecStore { this.externalDocs = this.parser.spec.externalDocs; this.contentItems = MenuBuilder.buildStructure(this.parser, this.options); this.securitySchemes = new SecuritySchemesModel(this.parser); - this.webhooks = new WebhookModel(this.parser, options, this.parser.spec['x-webhooks']); + const webhookPath: Referenced = {...this.parser?.spec?.['x-webhooks'], ...this.parser?.spec.webhooks}; + this.webhooks = new WebhookModel(this.parser, options, webhookPath); } } diff --git a/src/services/models/Webhook.ts b/src/services/models/Webhook.ts index 7349b8bd..6e8760f0 100644 --- a/src/services/models/Webhook.ts +++ b/src/services/models/Webhook.ts @@ -1,8 +1,8 @@ import { OpenAPIPath, Referenced } from '../../types'; import { OpenAPIParser } from '../OpenAPIParser'; import { OperationModel } from './Operation'; -import { isOperationName } from '../..'; import { RedocNormalizedOptions } from '../RedocNormalizedOptions'; +import { isOperationName } from '../..'; export class WebhookModel { operations: OperationModel[] = []; @@ -17,9 +17,17 @@ export class WebhookModel { for (const webhookName of Object.keys(webhooks)) { const webhook = webhooks[webhookName]; - const operations = Object.keys(webhook).filter(isOperationName); - for (const operationName of operations) { - const operationInfo = webhook[operationName]; + const operations = Object.keys(webhook); + for (let operationName of operations) { + let operationInfo = isOperationName(operationName) && webhook[operationName]; + + if (!isOperationName(operationName) && webhook[operationName].$ref) { + const resolvedOperationInfo = parser.deref(webhook[operationName] || {}) + operationInfo = resolvedOperationInfo + operationName = resolvedOperationInfo[Object.keys(resolvedOperationInfo)[0]] + } + + if (!operationInfo) continue; const operation = new OperationModel( parser, { diff --git a/src/utils/openapi.ts b/src/utils/openapi.ts index ff9c6afd..bd7db6b7 100644 --- a/src/utils/openapi.ts +++ b/src/utils/openapi.ts @@ -369,12 +369,12 @@ export function langFromMime(contentType: string): string { } export function isNamedDefinition(pointer?: string): boolean { - return /^#\/components\/schemas\/[^\/]+$/.test(pointer || ''); + return /^#\/components\/(schemas|pathItems)\/[^\/]+$/.test(pointer || ''); } export function getDefinitionName(pointer?: string): string | undefined { if (!pointer) return undefined; - const match = pointer.match(/^#\/components\/schemas\/([^\/]+)$/); + const match = pointer.match(/^#\/components\/(schemas|pathItems)\/([^\/]+)$/); return match === null ? undefined : match[1] }