@navios/openapi 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +8 -0
- package/README.md +260 -0
- package/dist/src/__tests__/decorators.spec.d.mts +2 -0
- package/dist/src/__tests__/decorators.spec.d.mts.map +1 -0
- package/dist/src/__tests__/metadata.spec.d.mts +2 -0
- package/dist/src/__tests__/metadata.spec.d.mts.map +1 -0
- package/dist/src/__tests__/services.spec.d.mts +2 -0
- package/dist/src/__tests__/services.spec.d.mts.map +1 -0
- package/dist/src/decorators/api-deprecated.decorator.d.mts +33 -0
- package/dist/src/decorators/api-deprecated.decorator.d.mts.map +1 -0
- package/dist/src/decorators/api-exclude.decorator.d.mts +26 -0
- package/dist/src/decorators/api-exclude.decorator.d.mts.map +1 -0
- package/dist/src/decorators/api-operation.decorator.d.mts +58 -0
- package/dist/src/decorators/api-operation.decorator.d.mts.map +1 -0
- package/dist/src/decorators/api-security.decorator.d.mts +36 -0
- package/dist/src/decorators/api-security.decorator.d.mts.map +1 -0
- package/dist/src/decorators/api-stream.decorator.d.mts +50 -0
- package/dist/src/decorators/api-stream.decorator.d.mts.map +1 -0
- package/dist/src/decorators/api-summary.decorator.d.mts +24 -0
- package/dist/src/decorators/api-summary.decorator.d.mts.map +1 -0
- package/dist/src/decorators/api-tag.decorator.d.mts +42 -0
- package/dist/src/decorators/api-tag.decorator.d.mts.map +1 -0
- package/dist/src/decorators/index.d.mts +8 -0
- package/dist/src/decorators/index.d.mts.map +1 -0
- package/dist/src/index.d.mts +5 -0
- package/dist/src/index.d.mts.map +1 -0
- package/dist/src/metadata/index.d.mts +2 -0
- package/dist/src/metadata/index.d.mts.map +1 -0
- package/dist/src/metadata/openapi.metadata.d.mts +30 -0
- package/dist/src/metadata/openapi.metadata.d.mts.map +1 -0
- package/dist/src/services/endpoint-scanner.service.d.mts +42 -0
- package/dist/src/services/endpoint-scanner.service.d.mts.map +1 -0
- package/dist/src/services/index.d.mts +6 -0
- package/dist/src/services/index.d.mts.map +1 -0
- package/dist/src/services/metadata-extractor.service.d.mts +19 -0
- package/dist/src/services/metadata-extractor.service.d.mts.map +1 -0
- package/dist/src/services/openapi-generator.service.d.mts +91 -0
- package/dist/src/services/openapi-generator.service.d.mts.map +1 -0
- package/dist/src/services/path-builder.service.d.mts +73 -0
- package/dist/src/services/path-builder.service.d.mts.map +1 -0
- package/dist/src/services/schema-converter.service.d.mts +57 -0
- package/dist/src/services/schema-converter.service.d.mts.map +1 -0
- package/dist/src/tokens/index.d.mts +18 -0
- package/dist/src/tokens/index.d.mts.map +1 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -0
- package/dist/tsconfig.spec.tsbuildinfo +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/tsdown.config.d.mts +3 -0
- package/dist/tsdown.config.d.mts.map +1 -0
- package/dist/vitest.config.d.mts +3 -0
- package/dist/vitest.config.d.mts.map +1 -0
- package/lib/index.cjs +2120 -0
- package/lib/index.cjs.map +1 -0
- package/lib/index.d.cts +594 -0
- package/lib/index.d.cts.map +1 -0
- package/lib/index.d.mts +594 -0
- package/lib/index.d.mts.map +1 -0
- package/lib/index.mjs +2077 -0
- package/lib/index.mjs.map +1 -0
- package/package.json +44 -0
- package/project.json +66 -0
- package/src/__tests__/decorators.spec.mts +185 -0
- package/src/__tests__/metadata.spec.mts +207 -0
- package/src/__tests__/services.spec.mts +216 -0
- package/src/decorators/api-deprecated.decorator.mts +45 -0
- package/src/decorators/api-exclude.decorator.mts +29 -0
- package/src/decorators/api-operation.decorator.mts +59 -0
- package/src/decorators/api-security.decorator.mts +44 -0
- package/src/decorators/api-stream.decorator.mts +55 -0
- package/src/decorators/api-summary.decorator.mts +33 -0
- package/src/decorators/api-tag.decorator.mts +51 -0
- package/src/decorators/index.mts +7 -0
- package/src/index.mts +42 -0
- package/src/metadata/index.mts +2 -0
- package/src/metadata/openapi.metadata.mts +30 -0
- package/src/services/endpoint-scanner.service.mts +118 -0
- package/src/services/index.mts +21 -0
- package/src/services/metadata-extractor.service.mts +91 -0
- package/src/services/openapi-generator.service.mts +219 -0
- package/src/services/path-builder.service.mts +344 -0
- package/src/services/schema-converter.service.mts +96 -0
- package/src/tokens/index.mts +24 -0
- package/tsconfig.json +24 -0
- package/tsconfig.lib.json +8 -0
- package/tsconfig.spec.json +12 -0
- package/tsdown.config.mts +35 -0
- package/vitest.config.mts +11 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ControllerMetadata,
|
|
3
|
+
HandlerMetadata,
|
|
4
|
+
ModuleMetadata,
|
|
5
|
+
} from '@navios/core'
|
|
6
|
+
import type { BaseEndpointConfig } from '@navios/builder'
|
|
7
|
+
|
|
8
|
+
import { extractControllerMetadata, inject, Injectable, Logger } from '@navios/core'
|
|
9
|
+
|
|
10
|
+
import type { OpenApiEndpointMetadata } from '../metadata/openapi.metadata.mjs'
|
|
11
|
+
|
|
12
|
+
import { MetadataExtractorService } from './metadata-extractor.service.mjs'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Represents a discovered endpoint with all its metadata
|
|
16
|
+
*/
|
|
17
|
+
export interface DiscoveredEndpoint {
|
|
18
|
+
/** Module metadata */
|
|
19
|
+
module: ModuleMetadata
|
|
20
|
+
/** Controller class */
|
|
21
|
+
controllerClass: any
|
|
22
|
+
/** Controller metadata */
|
|
23
|
+
controller: ControllerMetadata
|
|
24
|
+
/** Handler (endpoint) metadata */
|
|
25
|
+
handler: HandlerMetadata<any>
|
|
26
|
+
/** Endpoint configuration from @navios/builder */
|
|
27
|
+
config: BaseEndpointConfig
|
|
28
|
+
/** Extracted OpenAPI metadata */
|
|
29
|
+
openApiMetadata: OpenApiEndpointMetadata
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Service responsible for scanning modules and discovering endpoints.
|
|
34
|
+
*
|
|
35
|
+
* Iterates through all modules, controllers, and endpoints,
|
|
36
|
+
* extracting OpenAPI metadata from decorators.
|
|
37
|
+
*/
|
|
38
|
+
@Injectable()
|
|
39
|
+
export class EndpointScannerService {
|
|
40
|
+
private readonly logger = inject(Logger, {
|
|
41
|
+
context: EndpointScannerService.name,
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
private readonly metadataExtractor = inject(MetadataExtractorService)
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Scans all loaded modules and discovers endpoints.
|
|
48
|
+
*
|
|
49
|
+
* @param modules - Map of loaded modules from NaviosApplication
|
|
50
|
+
* @returns Array of discovered endpoints
|
|
51
|
+
*/
|
|
52
|
+
scan(modules: Map<string, ModuleMetadata>): DiscoveredEndpoint[] {
|
|
53
|
+
const endpoints: DiscoveredEndpoint[] = []
|
|
54
|
+
|
|
55
|
+
for (const [moduleName, moduleMetadata] of modules) {
|
|
56
|
+
if (!moduleMetadata.controllers || moduleMetadata.controllers.size === 0) {
|
|
57
|
+
continue
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
this.logger.debug(`Scanning module: ${moduleName}`)
|
|
61
|
+
|
|
62
|
+
for (const controllerClass of moduleMetadata.controllers) {
|
|
63
|
+
const controllerMeta = extractControllerMetadata(controllerClass)
|
|
64
|
+
const controllerEndpoints = this.scanController(
|
|
65
|
+
moduleMetadata,
|
|
66
|
+
controllerClass,
|
|
67
|
+
controllerMeta,
|
|
68
|
+
)
|
|
69
|
+
endpoints.push(...controllerEndpoints)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
this.logger.debug(`Discovered ${endpoints.length} endpoints`)
|
|
74
|
+
return endpoints
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Scans a controller and returns its endpoints
|
|
79
|
+
*/
|
|
80
|
+
private scanController(
|
|
81
|
+
module: ModuleMetadata,
|
|
82
|
+
controllerClass: any,
|
|
83
|
+
controllerMeta: ControllerMetadata,
|
|
84
|
+
): DiscoveredEndpoint[] {
|
|
85
|
+
const endpoints: DiscoveredEndpoint[] = []
|
|
86
|
+
|
|
87
|
+
for (const handler of controllerMeta.endpoints) {
|
|
88
|
+
// Skip endpoints without config (non-builder endpoints)
|
|
89
|
+
if (!handler.config) {
|
|
90
|
+
continue
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const openApiMetadata = this.metadataExtractor.extract(
|
|
94
|
+
controllerMeta,
|
|
95
|
+
handler,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
// Skip excluded endpoints
|
|
99
|
+
if (openApiMetadata.excluded) {
|
|
100
|
+
this.logger.debug(
|
|
101
|
+
`Skipping excluded endpoint: ${handler.classMethod}`,
|
|
102
|
+
)
|
|
103
|
+
continue
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
endpoints.push({
|
|
107
|
+
module,
|
|
108
|
+
controllerClass,
|
|
109
|
+
controller: controllerMeta,
|
|
110
|
+
handler,
|
|
111
|
+
config: handler.config as BaseEndpointConfig,
|
|
112
|
+
openApiMetadata,
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return endpoints
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export {
|
|
2
|
+
EndpointScannerService,
|
|
3
|
+
type DiscoveredEndpoint,
|
|
4
|
+
} from './endpoint-scanner.service.mjs'
|
|
5
|
+
|
|
6
|
+
export { MetadataExtractorService } from './metadata-extractor.service.mjs'
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
SchemaConverterService,
|
|
10
|
+
type SchemaConversionResult,
|
|
11
|
+
} from './schema-converter.service.mjs'
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
PathBuilderService,
|
|
15
|
+
type PathItemResult,
|
|
16
|
+
} from './path-builder.service.mjs'
|
|
17
|
+
|
|
18
|
+
export {
|
|
19
|
+
OpenApiGeneratorService,
|
|
20
|
+
type OpenApiGeneratorOptions,
|
|
21
|
+
} from './openapi-generator.service.mjs'
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { ControllerMetadata, HandlerMetadata } from '@navios/core'
|
|
2
|
+
|
|
3
|
+
import { Injectable } from '@navios/core'
|
|
4
|
+
|
|
5
|
+
import type { OpenApiEndpointMetadata } from '../metadata/openapi.metadata.mjs'
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
ApiDeprecatedToken,
|
|
9
|
+
ApiExcludeToken,
|
|
10
|
+
ApiOperationToken,
|
|
11
|
+
ApiSecurityToken,
|
|
12
|
+
ApiStreamToken,
|
|
13
|
+
ApiSummaryToken,
|
|
14
|
+
ApiTagToken,
|
|
15
|
+
} from '../tokens/index.mjs'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Service responsible for extracting OpenAPI metadata from decorators.
|
|
19
|
+
*
|
|
20
|
+
* Merges controller-level and handler-level metadata to produce
|
|
21
|
+
* a complete OpenAPI metadata object for each endpoint.
|
|
22
|
+
*/
|
|
23
|
+
@Injectable()
|
|
24
|
+
export class MetadataExtractorService {
|
|
25
|
+
/**
|
|
26
|
+
* Extracts and merges OpenAPI metadata from controller and handler.
|
|
27
|
+
*
|
|
28
|
+
* @param controller - Controller metadata
|
|
29
|
+
* @param handler - Handler metadata
|
|
30
|
+
* @returns Merged OpenAPI metadata
|
|
31
|
+
*/
|
|
32
|
+
extract(
|
|
33
|
+
controller: ControllerMetadata,
|
|
34
|
+
handler: HandlerMetadata<any>,
|
|
35
|
+
): OpenApiEndpointMetadata {
|
|
36
|
+
// Extract controller-level metadata
|
|
37
|
+
const controllerTag = controller.customAttributes.get(ApiTagToken) as
|
|
38
|
+
| { name: string; description?: string }
|
|
39
|
+
| undefined
|
|
40
|
+
|
|
41
|
+
// Extract handler-level metadata
|
|
42
|
+
const handlerTag = handler.customAttributes.get(ApiTagToken) as
|
|
43
|
+
| { name: string; description?: string }
|
|
44
|
+
| undefined
|
|
45
|
+
const operation = handler.customAttributes.get(ApiOperationToken) as
|
|
46
|
+
| {
|
|
47
|
+
summary?: string
|
|
48
|
+
description?: string
|
|
49
|
+
operationId?: string
|
|
50
|
+
deprecated?: boolean
|
|
51
|
+
externalDocs?: { url: string; description?: string }
|
|
52
|
+
}
|
|
53
|
+
| undefined
|
|
54
|
+
const summary = handler.customAttributes.get(ApiSummaryToken) as
|
|
55
|
+
| string
|
|
56
|
+
| undefined
|
|
57
|
+
const deprecated = handler.customAttributes.get(ApiDeprecatedToken) as
|
|
58
|
+
| { message?: string }
|
|
59
|
+
| undefined
|
|
60
|
+
const security = handler.customAttributes.get(ApiSecurityToken) as
|
|
61
|
+
| Record<string, string[]>
|
|
62
|
+
| undefined
|
|
63
|
+
const excluded = handler.customAttributes.get(ApiExcludeToken) as
|
|
64
|
+
| boolean
|
|
65
|
+
| undefined
|
|
66
|
+
const stream = handler.customAttributes.get(ApiStreamToken) as
|
|
67
|
+
| { contentType: string; description?: string }
|
|
68
|
+
| undefined
|
|
69
|
+
|
|
70
|
+
// Build tags array (handler tag takes precedence but both are included)
|
|
71
|
+
const tags: string[] = []
|
|
72
|
+
if (controllerTag?.name) {
|
|
73
|
+
tags.push(controllerTag.name)
|
|
74
|
+
}
|
|
75
|
+
if (handlerTag?.name && handlerTag.name !== controllerTag?.name) {
|
|
76
|
+
tags.push(handlerTag.name)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
tags,
|
|
81
|
+
summary: operation?.summary ?? summary,
|
|
82
|
+
description: operation?.description,
|
|
83
|
+
operationId: operation?.operationId,
|
|
84
|
+
deprecated: deprecated !== undefined || operation?.deprecated === true,
|
|
85
|
+
externalDocs: operation?.externalDocs,
|
|
86
|
+
security: security ? [security] : undefined,
|
|
87
|
+
excluded: excluded === true,
|
|
88
|
+
stream,
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import type { ModuleMetadata } from '@navios/core'
|
|
2
|
+
import type { oas31 } from 'zod-openapi'
|
|
3
|
+
|
|
4
|
+
import { inject, Injectable, Logger } from '@navios/core'
|
|
5
|
+
|
|
6
|
+
import type { DiscoveredEndpoint } from './endpoint-scanner.service.mjs'
|
|
7
|
+
|
|
8
|
+
import { EndpointScannerService } from './endpoint-scanner.service.mjs'
|
|
9
|
+
import { PathBuilderService } from './path-builder.service.mjs'
|
|
10
|
+
|
|
11
|
+
type OpenAPIObject = oas31.OpenAPIObject
|
|
12
|
+
type PathsObject = oas31.PathsObject
|
|
13
|
+
type SecuritySchemeObject = oas31.SecuritySchemeObject
|
|
14
|
+
type TagObject = oas31.TagObject
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Options for generating the OpenAPI document
|
|
18
|
+
*/
|
|
19
|
+
export interface OpenApiGeneratorOptions {
|
|
20
|
+
/**
|
|
21
|
+
* OpenAPI document info
|
|
22
|
+
*/
|
|
23
|
+
info: {
|
|
24
|
+
title: string
|
|
25
|
+
version: string
|
|
26
|
+
description?: string
|
|
27
|
+
termsOfService?: string
|
|
28
|
+
contact?: {
|
|
29
|
+
name?: string
|
|
30
|
+
url?: string
|
|
31
|
+
email?: string
|
|
32
|
+
}
|
|
33
|
+
license?: {
|
|
34
|
+
name: string
|
|
35
|
+
url?: string
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* External documentation
|
|
41
|
+
*/
|
|
42
|
+
externalDocs?: {
|
|
43
|
+
url: string
|
|
44
|
+
description?: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Server definitions
|
|
49
|
+
*/
|
|
50
|
+
servers?: Array<{
|
|
51
|
+
url: string
|
|
52
|
+
description?: string
|
|
53
|
+
variables?: Record<
|
|
54
|
+
string,
|
|
55
|
+
{
|
|
56
|
+
default: string
|
|
57
|
+
enum?: string[]
|
|
58
|
+
description?: string
|
|
59
|
+
}
|
|
60
|
+
>
|
|
61
|
+
}>
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Security scheme definitions
|
|
65
|
+
*/
|
|
66
|
+
securitySchemes?: Record<string, SecuritySchemeObject>
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Global security requirements
|
|
70
|
+
*/
|
|
71
|
+
security?: Array<Record<string, string[]>>
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Tag definitions with descriptions
|
|
75
|
+
*/
|
|
76
|
+
tags?: TagObject[]
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Service responsible for generating the complete OpenAPI document.
|
|
81
|
+
*
|
|
82
|
+
* Orchestrates endpoint discovery, path generation, and document assembly.
|
|
83
|
+
*/
|
|
84
|
+
@Injectable()
|
|
85
|
+
export class OpenApiGeneratorService {
|
|
86
|
+
private readonly logger = inject(Logger, {
|
|
87
|
+
context: OpenApiGeneratorService.name,
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
private readonly scanner = inject(EndpointScannerService)
|
|
91
|
+
private readonly pathBuilder = inject(PathBuilderService)
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Generates an OpenAPI document from loaded modules.
|
|
95
|
+
*
|
|
96
|
+
* @param modules - Map of loaded modules
|
|
97
|
+
* @param options - OpenAPI generation options
|
|
98
|
+
* @returns Complete OpenAPI document
|
|
99
|
+
*/
|
|
100
|
+
generate(
|
|
101
|
+
modules: Map<string, ModuleMetadata>,
|
|
102
|
+
options: OpenApiGeneratorOptions,
|
|
103
|
+
): OpenAPIObject {
|
|
104
|
+
this.logger.debug('Generating OpenAPI document')
|
|
105
|
+
|
|
106
|
+
// Discover all endpoints
|
|
107
|
+
const endpoints = this.scanner.scan(modules)
|
|
108
|
+
|
|
109
|
+
// Generate paths
|
|
110
|
+
const paths = this.buildPaths(endpoints)
|
|
111
|
+
|
|
112
|
+
// Collect unique tags from endpoints
|
|
113
|
+
const discoveredTags = this.collectTags(endpoints)
|
|
114
|
+
|
|
115
|
+
// Merge discovered tags with configured tags
|
|
116
|
+
const tags = this.mergeTags(discoveredTags, options.tags)
|
|
117
|
+
|
|
118
|
+
// Build the OpenAPI document
|
|
119
|
+
const document: OpenAPIObject = {
|
|
120
|
+
openapi: '3.1.0',
|
|
121
|
+
info: options.info,
|
|
122
|
+
paths,
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Add optional fields
|
|
126
|
+
if (options.servers && options.servers.length > 0) {
|
|
127
|
+
document.servers = options.servers
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (options.externalDocs) {
|
|
131
|
+
document.externalDocs = options.externalDocs
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (tags.length > 0) {
|
|
135
|
+
document.tags = tags
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (options.security) {
|
|
139
|
+
document.security = options.security
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (options.securitySchemes) {
|
|
143
|
+
document.components = {
|
|
144
|
+
...document.components,
|
|
145
|
+
securitySchemes: options.securitySchemes,
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
this.logger.debug(
|
|
150
|
+
`Generated OpenAPI document with ${Object.keys(paths).length} paths`,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
return document
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Builds paths object from discovered endpoints
|
|
158
|
+
*/
|
|
159
|
+
private buildPaths(endpoints: DiscoveredEndpoint[]): PathsObject {
|
|
160
|
+
const paths: PathsObject = {}
|
|
161
|
+
|
|
162
|
+
for (const endpoint of endpoints) {
|
|
163
|
+
const { path, pathItem } = this.pathBuilder.build(endpoint)
|
|
164
|
+
|
|
165
|
+
// Merge with existing path if methods differ
|
|
166
|
+
if (paths[path]) {
|
|
167
|
+
paths[path] = {
|
|
168
|
+
...paths[path],
|
|
169
|
+
...pathItem,
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
paths[path] = pathItem
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return paths
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Collects unique tags from endpoints
|
|
181
|
+
*/
|
|
182
|
+
private collectTags(endpoints: DiscoveredEndpoint[]): Set<string> {
|
|
183
|
+
const tags = new Set<string>()
|
|
184
|
+
|
|
185
|
+
for (const endpoint of endpoints) {
|
|
186
|
+
for (const tag of endpoint.openApiMetadata.tags) {
|
|
187
|
+
tags.add(tag)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return tags
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Merges discovered tags with configured tags
|
|
196
|
+
*/
|
|
197
|
+
private mergeTags(
|
|
198
|
+
discoveredTags: Set<string>,
|
|
199
|
+
configuredTags?: TagObject[],
|
|
200
|
+
): TagObject[] {
|
|
201
|
+
const tagMap = new Map<string, TagObject>()
|
|
202
|
+
|
|
203
|
+
// Add configured tags first (they have descriptions)
|
|
204
|
+
if (configuredTags) {
|
|
205
|
+
for (const tag of configuredTags) {
|
|
206
|
+
tagMap.set(tag.name, tag)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Add discovered tags that aren't already configured
|
|
211
|
+
for (const tagName of discoveredTags) {
|
|
212
|
+
if (!tagMap.has(tagName)) {
|
|
213
|
+
tagMap.set(tagName, { name: tagName })
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return Array.from(tagMap.values())
|
|
218
|
+
}
|
|
219
|
+
}
|