Skip to content

Commit 136d5ce

Browse files
committed
feat: Refactoring
1 parent 26cc1fd commit 136d5ce

File tree

3 files changed

+121
-89
lines changed

3 files changed

+121
-89
lines changed

src/apitypes/rest/rest.operation.ts

Lines changed: 44 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -214,12 +214,17 @@ export const calculateSpecRefs = (sourceDocument: unknown, normalizedSpec: unkno
214214
return
215215
}
216216
const componentName = matchResult.grepValues[grepKey].toString()
217-
let sourceComponents = getKeyValue(sourceDocument, ...matchResult.path) as unknown
217+
let sourceComponents = getKeyValue(sourceDocument, ...matchResult.path)
218218
if (!sourceComponents || typeof sourceComponents !== 'object') {
219219
return
220220
}
221-
sourceComponents = prunePathItemMethods(sourceComponents, resultSpec, matchResult.path)
222-
// component is defined and object at this point
221+
222+
if (typeof sourceComponents === 'object') {
223+
const allowedOps = getAllowedHttpOps(resultSpec, matchResult.path)
224+
if (allowedOps.length > 0 && isComponentsPathItemRef(matchResult.path)) {
225+
sourceComponents = filterPathItemOperations(sourceComponents, allowedOps)
226+
}
227+
}
223228
if (models && !models[componentName] && isComponentsSchemaRef(matchResult.path)) {
224229
const existingHash = componentsHashMap?.get(componentName)
225230
if (existingHash) {
@@ -247,41 +252,22 @@ export const isComponentsPathItemRef = (path: JsonPath): boolean => {
247252
)
248253
}
249254

250-
/**
251-
* Returns a shallow-copied object with HTTP method keys pruned to the allowed set,
252-
* but only when the jsonPath points to components.pathItems.
253-
*
254-
* If jsonPath does not match components.pathItems or allowedOps is empty, returns input unchanged.
255-
*/
256-
export const prunePathItemMethods = (
255+
export const filterPathItemOperations = (
257256
source: unknown,
258-
resultSpec: unknown,
259-
jsonPath: JsonPath,
257+
allowedMethods: string[],
260258
): unknown => {
261-
const allowedOps = getAllowedHttpOps(resultSpec, jsonPath)
262-
if (!source || typeof source !== 'object') {
263-
return source
264-
}
265-
if (allowedOps.length === 0) {
266-
return source
267-
}
268-
if (!isComponentsPathItemRef(jsonPath)) {
269-
return source
270-
}
271259
const httpMethods = new Set<string>(Object.values(OpenAPIV3.HttpMethods) as string[])
272-
const copy: Record<string, unknown> = { ...(source as Record<string, unknown>) }
273-
Object.keys(copy).forEach((key: string) => {
274-
if (httpMethods.has(key) && !allowedOps.includes(key)) {
275-
delete copy[key]
260+
const filteredSource: Record<string, unknown> = { ...(source as Record<string, unknown>) }
261+
262+
for (const key of Object.keys(filteredSource)) {
263+
if (httpMethods.has(key) && !allowedMethods.includes(key)) {
264+
delete filteredSource[key]
276265
}
277-
})
278-
return copy
266+
}
267+
268+
return filteredSource
279269
}
280270

281-
/**
282-
* Extracts the set of HTTP methods allowed by the partial component found at jsonPath
283-
* within the resultSpec. If the path does not resolve to an object, returns an empty list.
284-
*/
285271
export const getAllowedHttpOps = (resultSpec: unknown, jsonPath: JsonPath): string[] => {
286272
const resultComponents = getKeyValue(resultSpec, ...jsonPath) as unknown
287273
if (typeof resultComponents !== 'object' || resultComponents === null) {
@@ -310,41 +296,50 @@ const createSingleOperationSpec = (
310296
security?: OpenAPIV3.SecurityRequirementObject[],
311297
securitySchemes?: { [p: string]: OpenAPIV3.ReferenceObject | OpenAPIV3.SecuritySchemeObject },
312298
): TYPE.RestOperationData => {
313-
const pathData = document.paths[path] as OpenAPIV3.PathItemObject
299+
const pathData = document.paths[path] as OpenAPIV3.PathItemObject | undefined
300+
if (!pathData) {
301+
throw new Error(`Path "${path}" not found in the document`)
302+
}
303+
304+
const baseSpec = {
305+
openapi: openapi ?? '3.0.0',
306+
...takeIfDefined({ servers }),
307+
...takeIfDefined({ security }), // TODO: remove duplicates in security
308+
components: {
309+
...takeIfDefined({ securitySchemes }),
310+
},
311+
}
312+
313+
if (pathData.$ref) {
314+
const cleanedDocument = resolveRefAndMap(
315+
document,
316+
pathData.$ref,
317+
(pathItemObject: OpenAPIV3.PathItemObject) => ({
318+
...extractCommonPathItemProperties(pathItemObject),
319+
[method]: { ...pathItemObject[method] },
320+
}),
321+
)
314322

315-
const ref = pathData?.$ref
316-
if (pathData && ref) {
317-
const cleanedDocument = resolveRefAndMap(document, ref, (pathItemObject: OpenAPIV3.PathItemObject) => ({
318-
...extractCommonPathItemProperties(pathItemObject),
319-
[method]: { ...pathItemObject[method] },
320-
}))
321323
return {
322-
openapi: openapi ?? '3.0.0',
323-
...takeIfDefined({ servers }),
324-
...takeIfDefined({ security }), // TODO: remove duplicates in security
324+
...baseSpec,
325325
paths: {
326326
[path]: pathData,
327327
},
328328
components: {
329-
...takeIfDefined({ securitySchemes }),
329+
...baseSpec.components,
330330
...cleanedDocument?.components ?? {},
331331
},
332332
}
333333
}
334334

335335
return {
336-
openapi: openapi ?? '3.0.0',
337-
...takeIfDefined({ servers }),
338-
...takeIfDefined({ security }), // TODO: remove duplicates in security
336+
...baseSpec,
339337
paths: {
340338
[path]: {
341339
...extractCommonPathItemProperties(pathData),
342340
[method]: { ...pathData[method] },
343341
},
344342
},
345-
components: {
346-
...takeIfDefined({ securitySchemes }),
347-
},
348343
}
349344
}
350345

src/strategies/document-group.strategy.ts

Lines changed: 77 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -121,71 +121,111 @@ function transformDocumentData(versionDocument: VersionDocument): OpenAPIV3.Docu
121121
paths: {},
122122
}
123123

124-
const normalizedPathKeys = Object.keys(normalizedPaths)
125-
for (const path of normalizedPathKeys) {
124+
for (const path of Object.keys(normalizedPaths)) {
126125
const sourcePathItem = sourcePaths[path]
127126
const normalizedPathItem = normalizedPaths[path]
128127

129128
if (!isNonNullObject(sourcePathItem) || !isNonNullObject(normalizedPathItem)) {
130129
continue
131130
}
132131

133-
const commonPathProps = extractCommonPathItemProperties(sourcePathItem)
132+
const commonProps = extractCommonPathItemProperties(sourcePathItem)
134133

135-
const methodKeys = Object.keys(normalizedPathItem)
136-
for (const method of methodKeys) {
137-
const inferredMethod = method as OpenAPIV3.HttpMethods
134+
for (const method of Object.keys(normalizedPathItem)) {
135+
const httpMethod = method as OpenAPIV3.HttpMethods
136+
if (!isValidHttpMethod(httpMethod)) continue
138137

139-
// check if field is a valid openapi http method defined in OpenAPIV3.HttpMethods
140-
if (!isValidHttpMethod(inferredMethod)) {
141-
continue
142-
}
138+
const methodData = normalizedPathItem[httpMethod]
139+
const basePath = getOperationBasePath(
140+
methodData?.servers ||
141+
sourcePathItem?.servers ||
142+
sourceDocument?.servers ||
143+
[],
144+
)
143145

144-
const methodData = normalizedPathItem[inferredMethod]
145-
const basePath = getOperationBasePath(methodData?.servers || sourcePathItem?.servers || sourceDocument?.servers || [])
146146
const operationPath = basePath + path
147147
const operationId = slugify(`${removeFirstSlash(operationPath)}-${method}`)
148148

149149
if (!versionDocument.operationIds.includes(operationId)) {
150150
continue
151151
}
152152

153-
const pathItemRef = sourcePathItem?.$ref
154153
const pathData = sourceDocument.paths[path]!
155-
if (pathItemRef) {
156-
const targetFromResultDocument = getParentValueByRef(resultDocument, pathData.$ref ?? '')
157-
const target = resolveRefAndMap(sourceDocument, pathData.$ref ?? '', (pathItemObject: OpenAPIV3.PathItemObject) => ({
158-
...targetFromResultDocument,
159-
...extractCommonPathItemProperties(pathItemObject),
160-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
161-
// @ts-ignore
162-
[method]: { ...pathItemObject[method] },
163-
}))
164-
165-
resultDocument.paths[path] = pathData
166-
167-
resultDocument.components = {
168-
...takeIfDefined({ securitySchemes: sourceComponents?.securitySchemes }),
169-
...target.components ?? {},
170-
}
154+
if (sourcePathItem?.$ref) {
155+
handleRefPathItem(
156+
resultDocument,
157+
sourceDocument,
158+
path,
159+
pathData,
160+
httpMethod,
161+
)
171162
} else {
172-
const existingPath = resultDocument.paths[path]
173-
resultDocument.paths[path] = {
174-
...existingPath,
175-
...commonPathProps,
176-
[inferredMethod]: { ...pathData[inferredMethod] },
177-
}
163+
handleInlinePathItem(
164+
resultDocument,
165+
path,
166+
pathData,
167+
httpMethod,
168+
commonProps,
169+
)
170+
}
171+
172+
if (sourceComponents?.securitySchemes) {
178173
resultDocument.components = {
179-
...takeIfDefined({ securitySchemes: sourceComponents?.securitySchemes }),
174+
...resultDocument.components,
175+
securitySchemes: sourceComponents.securitySchemes,
180176
}
181177
}
182-
183178
}
184179
}
185180

186181
return resultDocument
187182
}
188183

184+
function handleRefPathItem(
185+
resultDoc: OpenAPIV3.Document,
186+
sourceDoc: OpenAPIV3.Document,
187+
path: string,
188+
pathData: OpenAPIV3.PathItemObject | OpenAPIV3.ReferenceObject,
189+
method: OpenAPIV3.HttpMethods,
190+
): void {
191+
const targetFromResultDoc = getParentValueByRef(resultDoc, (pathData as any).$ref ?? '')
192+
193+
const target = resolveRefAndMap(
194+
sourceDoc,
195+
(pathData as any).$ref ?? '',
196+
(pathItemObject: OpenAPIV3.PathItemObject) => ({
197+
...targetFromResultDoc,
198+
...extractCommonPathItemProperties(pathItemObject),
199+
[method]: { ...pathItemObject[method] },
200+
}),
201+
)
202+
203+
resultDoc.paths[path] = pathData
204+
205+
if (target.components) {
206+
resultDoc.components = {
207+
...resultDoc.components,
208+
...target.components,
209+
}
210+
}
211+
}
212+
213+
function handleInlinePathItem(
214+
resultDoc: OpenAPIV3.Document,
215+
path: string,
216+
pathData: OpenAPIV3.PathItemObject,
217+
method: OpenAPIV3.HttpMethods,
218+
commonProps: Partial<OpenAPIV3.PathItemObject>,
219+
): void {
220+
const existingPath = resultDoc.paths[path]
221+
222+
resultDoc.paths[path] = {
223+
...existingPath,
224+
...commonProps,
225+
[method]: { ...pathData[method] },
226+
}
227+
}
228+
189229
function normalizeOpenApi(document: OpenAPIV3.Document, source?: OpenAPIV3.Document): OpenAPIV3.Document {
190230
return normalize(
191231
document,

src/utils/builder.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -244,18 +244,15 @@ export const resolveRefAndMap = (
244244
const value = { ...rawValue }
245245

246246
if (isRecordObject(value) && typeof value.$ref === 'string') {
247-
// preserve intermediate referenced node
248247
setValueByPath(result, jsonPath, value)
249248
currentRef = value.$ref
250249
continue
251250
}
252251

253-
// terminal value reached; apply mapper
254252
setValueByPath(result, jsonPath, valueMapper(value))
255253
return result
256254
}
257255

258-
// Fallback when loop breaks due to cycle or missing ref
259256
if (lastPath.length) {
260257
const terminal = getValueByJsonPath(document, lastPath)
261258
if (terminal !== undefined) {

0 commit comments

Comments
 (0)