Skip to content

Commit bd0fd4c

Browse files
committed
feat: add info, externalDocs, tags from source document to single operation specification
1 parent e8b16e5 commit bd0fd4c

File tree

6 files changed

+117
-11
lines changed

6 files changed

+117
-11
lines changed

src/apitypes/rest/rest.operation.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import {
4747
takeIf,
4848
takeIfDefined,
4949
} from '../../utils'
50+
import { getUsedTags } from '../../utils/mergeOpenapiDocuments'
5051
import { API_KIND, INLINE_REFS_FLAG, ORIGINS_SYMBOL, VERSION_STATUS } from '../../consts'
5152
import { extractSecuritySchemesNames, getCustomTags, resolveApiAudience } from './rest.utils'
5253
import { DebugPerformanceContext, syncDebugPerformance } from '../../utils/logs'
@@ -328,6 +329,9 @@ const isOperationPaths = (paths: JsonPath[]): boolean => {
328329
// todo output of this method disrupts document normalization.
329330
// origin symbols are not being transferred to the resulting spec.
330331
// DO NOT pass output of this method to apiDiff
332+
// TODO: conceptually, this method does processing which is very similar
333+
// is very similar to the reducedSourceSpecifications transformation.
334+
// We should merge these two functions into one.
331335
export const createSingleOperationSpec = (
332336
document: OpenAPIV3.Document,
333337
path: string,
@@ -350,8 +354,12 @@ export const createSingleOperationSpec = (
350354
: undefined
351355

352356
const isRefPathData = !!pathData.$ref
353-
return {
357+
358+
// Construct the single operation document
359+
const singleOperationDocument: TYPE.RestOperationData = {
354360
openapi: openapi ?? '3.0.0',
361+
...takeIfDefined({ info: document.info }),
362+
...takeIfDefined({ externalDocs: document.externalDocs }),
355363
...takeIfDefined({ servers }),
356364
...!operationSecurity ? takeIfDefined({ security }) : {},// Only add root security if operation security is not explicitly defined
357365
paths: {
@@ -367,8 +375,20 @@ export const createSingleOperationSpec = (
367375
components: effectiveSecuritySchemes ? { securitySchemes: effectiveSecuritySchemes } : undefined,
368376
}),
369377
}
370-
}
371378

379+
// Filter tags to only include those used by this operation
380+
if (document.tags) {
381+
const filteredTags = getUsedTags([{
382+
...singleOperationDocument,
383+
tags: document.tags,
384+
} as OpenAPIV3.Document])
385+
if (filteredTags) {
386+
singleOperationDocument.tags = filteredTags
387+
}
388+
}
389+
390+
return singleOperationDocument
391+
}
372392
export const extractCommonPathItemProperties = (
373393
pathData: OpenAPIV3.PathItemObject,
374394
): Pick<OpenAPIV3.PathItemObject, 'summary' | 'description' | 'servers' | 'parameters'> => ({

src/apitypes/rest/rest.types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,13 @@ export type VersionRestOperation = ApiOperation<RestOperationData, RestOperation
4747

4848
export interface RestOperationData {
4949
openapi: string
50+
info?: OpenAPIV3.InfoObject
5051
servers?: OpenAPIV3.ServerObject[]
5152
paths: OpenAPIV3.PathsObject
5253
components?: OpenAPIV3.ComponentsObject
5354
security?: OpenAPIV3.SecurityRequirementObject[]
55+
externalDocs?: OpenAPIV3.ExternalDocumentationObject
56+
tags?: OpenAPIV3.TagObject[]
5457
}
5558

5659
export interface RestRefCache {

src/utils/mergeOpenapiDocuments.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export const mergeOpenapiDocuments = (documents: OpenAPIV3.Document[], info: Ope
5656
paths: merged.paths,
5757
components: merged.components,
5858
security: template?.security || merged.security,
59-
...takeIfDefined({ tags: getTags(documents) }),
59+
...takeIfDefined({ tags: getUsedTags(documents) }),
6060
...takeIfDefined({ externalDocs: template?.externalDocs || getExternalDocs(merged, diffs) }),
6161
}
6262
}
@@ -112,9 +112,9 @@ function extractDeclarationPaths(diff: Diff): JsonPath[] {
112112

113113
function compliesWithRules(rules: DiffRule[], diff: Diff): boolean {
114114
return rules.some(allowedDiff => {
115-
const matchResult = matchPaths(extractDeclarationPaths(diff), allowedDiff.pathTemplate)
116-
return matchResult?.path && allowedDiff.allowedActions.includes(diff.action)
117-
},
115+
const matchResult = matchPaths(extractDeclarationPaths(diff), allowedDiff.pathTemplate)
116+
return matchResult?.path && allowedDiff.allowedActions.includes(diff.action)
117+
},
118118
)
119119
}
120120

@@ -133,7 +133,7 @@ const validateResult = (rules: DiffRule[], diffs: Diff[], title1: string, title2
133133
}
134134
}
135135

136-
function getTags(specs: OpenAPIV3.Document[]): OpenAPIV3.TagObject[] | undefined {
136+
export function getUsedTags(specs: OpenAPIV3.Document[]): OpenAPIV3.TagObject[] | undefined {
137137
const tagsWithUsages = specs
138138
.map(({ tags, paths }) => {
139139
const specTags: string[] = []
@@ -198,10 +198,10 @@ function prepareTemplate(openapi: string, template?: ExportTemplate): ExportTemp
198198
info,
199199
...takeIfDefined({ servers }),
200200
...takeIf({
201-
components: {
202-
securitySchemes,
203-
},
204-
}, !!securitySchemes,
201+
components: {
202+
securitySchemes,
203+
},
204+
}, !!securitySchemes,
205205
),
206206
...takeIfDefined({ security }),
207207
...takeIfDefined({ externalDocs }),
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
openapi: 3.0.3
2+
info:
3+
title: Info and ExternalDocs without Tags Test
4+
version: 1.0.0
5+
description: Test document for info and externalDocs when no tags are present
6+
externalDocs:
7+
description: API Documentation
8+
url: https://docs.example.com
9+
paths:
10+
/test:
11+
get:
12+
summary: Test operation without tags
13+
responses:
14+
"200":
15+
description: OK
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
openapi: 3.0.3
2+
info:
3+
title: Info, ExternalDocs, and Tags Filtering Test
4+
version: 1.0.0
5+
description: Test document for filtering info, externalDocs, and tags
6+
contact:
7+
name: API Support
8+
9+
externalDocs:
10+
description: API Documentation
11+
url: https://docs.example.com
12+
tags:
13+
- name: pet
14+
description: Pet operations
15+
- name: store
16+
description: Store operations
17+
- name: user
18+
description: User operations
19+
paths:
20+
/test:
21+
get:
22+
summary: Test operation
23+
tags:
24+
- pet
25+
responses:
26+
"200":
27+
description: OK

test/rest.operation.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,5 +122,46 @@ describe('REST Operation Unit Tests', () => {
122122
expect(result.security).toEqual(document.security)
123123
})
124124
})
125+
126+
describe('Info, ExternalDocs, and Tags Handling', () => {
127+
test('should include info object from source document', async () => {
128+
const document = await loadYamlFile('rest.operation/info-externaldocs-tags-filtering/base.yaml')
129+
130+
const result = createTestSingleOperationSpec(document)
131+
132+
expect(result.info).toBeDefined()
133+
expect(result.info).toEqual(document.info)
134+
})
135+
136+
test('should include externalDocs object from source document', async () => {
137+
const document = await loadYamlFile('rest.operation/info-externaldocs-tags-filtering/base.yaml')
138+
139+
const result = createTestSingleOperationSpec(document)
140+
141+
expect(result.externalDocs).toBeDefined()
142+
expect(result.externalDocs).toEqual(document.externalDocs)
143+
})
144+
145+
test('should filter tags to only include those used by the operation', async () => {
146+
const document = await loadYamlFile('rest.operation/info-externaldocs-tags-filtering/base.yaml')
147+
148+
const result = createTestSingleOperationSpec(document)
149+
150+
expect(result.tags).toEqual([
151+
{
152+
name: 'pet',
153+
description: 'Pet operations',
154+
},
155+
])
156+
})
157+
158+
test('should handle document with no tags', async () => {
159+
const document = await loadYamlFile('rest.operation/info-externaldocs-no-tags/base.yaml')
160+
161+
const result = createTestSingleOperationSpec(document)
162+
163+
expect(result.tags).toBeUndefined()
164+
})
165+
})
125166
})
126167
})

0 commit comments

Comments
 (0)