@@ -19,38 +19,41 @@ import { OpenAPIV3 } from 'openapi-types'
1919import { REST_API_TYPE , REST_KIND_KEY } from './rest.consts'
2020import { operationRules } from './rest.rules'
2121import type * as TYPE from './rest.types'
22- import type {
22+ import { RestOperationData } from './rest.types'
23+ import {
2324 BuildConfig ,
2425 CrawlRule ,
2526 DeprecateItem ,
2627 NotificationMessage ,
2728 OperationCrawlState ,
29+ OperationId ,
2830 SearchScopes ,
2931} from '../../types'
3032import {
3133 buildSearchScope ,
3234 capitalize ,
3335 getKeyValue ,
3436 getSplittedVersionKey ,
35- getValueByRefAndUpdate ,
3637 isDeprecatedOperationItem ,
38+ isObject ,
3739 isOperationDeprecated ,
3840 normalizePath ,
3941 rawToApiKind ,
42+ removeFirstSlash ,
4043 setValueByPath ,
44+ slugify ,
4145 takeIf ,
4246 takeIfDefined ,
4347} from '../../utils'
4448import { API_KIND , INLINE_REFS_FLAG , ORIGINS_SYMBOL , VERSION_STATUS } from '../../consts'
45- import { getCustomTags , resolveApiAudience } from './rest.utils'
49+ import { getCustomTags , getOperationBasePath , resolveApiAudience } from './rest.utils'
4650import { DebugPerformanceContext , syncDebugPerformance } from '../../utils/logs'
4751import {
4852 calculateDeprecatedItems ,
4953 grepValue ,
5054 JSON_SCHEMA_PROPERTY_DEPRECATED ,
5155 matchPaths ,
5256 OPEN_API_PROPERTY_COMPONENTS ,
53- OPEN_API_PROPERTY_PATH_ITEMS ,
5457 OPEN_API_PROPERTY_PATHS ,
5558 OPEN_API_PROPERTY_SCHEMAS ,
5659 parseRef ,
@@ -61,6 +64,7 @@ import {
6164} from '@netcracker/qubership-apihub-api-unifier'
6265import { calculateObjectHash } from '../../utils/hashes'
6366import { calculateTolerantHash } from '../../components/deprecated'
67+ import { getValueByPath } from '../../utils/path'
6468
6569export const buildRestOperation = (
6670 operationId : string ,
@@ -145,7 +149,7 @@ export const buildRestOperation = (
145149 security ,
146150 components ?. securitySchemes ,
147151 )
148- calculateSpecRefs ( document . data , refsOnlySingleOperationSpec , specWithSingleOperation , models , componentsHashMap )
152+ calculateSpecRefs ( document . data , refsOnlySingleOperationSpec , specWithSingleOperation , [ operationId ] , models , componentsHashMap )
149153 const dataHash = calculateObjectHash ( specWithSingleOperation )
150154 return [ specWithSingleOperation , dataHash ]
151155 } , debugCtx )
@@ -182,7 +186,14 @@ export const buildRestOperation = (
182186 }
183187}
184188
185- export const calculateSpecRefs = ( sourceDocument : unknown , normalizedSpec : unknown , resultSpec : unknown , models ?: Record < string , string > , componentsHashMap ?: Map < string , string > ) : void => {
189+ export const calculateSpecRefs = (
190+ sourceDocument : TYPE . RestOperationData ,
191+ normalizedSpec : TYPE . RestOperationData ,
192+ resultSpec : TYPE . RestOperationData ,
193+ operations : OperationId [ ] ,
194+ models ?: Record < string , string > ,
195+ componentsHashMap ?: Map < string , string > ,
196+ ) : void => {
186197 const handledObjects = new Set < unknown > ( )
187198 const inlineRefs = new Set < string > ( )
188199 syncCrawl (
@@ -214,67 +225,94 @@ export const calculateSpecRefs = (sourceDocument: unknown, normalizedSpec: unkno
214225 return
215226 }
216227 const componentName = matchResult . grepValues [ grepKey ] . toString ( )
217- let sourceComponents = getKeyValue ( sourceDocument , ...matchResult . path )
218- if ( ! sourceComponents || typeof sourceComponents !== 'object' ) {
228+ let component = getKeyValue ( sourceDocument , ...matchResult . path ) as Record < string , unknown >
229+ if ( ! component ) {
219230 return
220231 }
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- }
232+ if ( isObject ( component ) ) {
233+ component = { ...component }
227234 }
228235 if ( models && ! models [ componentName ] && isComponentsSchemaRef ( matchResult . path ) ) {
229- const existingHash = componentsHashMap ?. get ( componentName )
230- if ( existingHash ) {
231- models [ componentName ] = existingHash
236+ let componentHash = componentsHashMap ?. get ( componentName )
237+ if ( componentHash ) {
238+ models [ componentName ] = componentHash
232239 } else {
233- const componentHashCalculated = calculateObjectHash ( sourceComponents as object )
234- componentsHashMap ?. set ( componentName , componentHashCalculated )
235- models [ componentName ] = componentHashCalculated
240+ componentHash = calculateObjectHash ( component )
241+ componentsHashMap ?. set ( componentName , componentHash )
242+ models [ componentName ] = componentHash
236243 }
237244 }
238- setValueByPath ( resultSpec , matchResult . path , sourceComponents )
245+
246+ setValueByPath ( resultSpec , matchResult . path , component )
239247 } )
240- }
241248
242- export const isComponentsSchemaRef = ( path : JsonPath ) : boolean => {
243- return ! ! matchPaths (
244- [ path ] ,
245- [ [ OPEN_API_PROPERTY_COMPONENTS , OPEN_API_PROPERTY_SCHEMAS , PREDICATE_UNCLOSED_END ] ] ,
246- )
247- }
248- export const isComponentsPathItemRef = ( path : JsonPath ) : boolean => {
249- return ! ! matchPaths (
250- [ path ] ,
251- [ [ OPEN_API_PROPERTY_COMPONENTS , OPEN_API_PROPERTY_PATH_ITEMS , PREDICATE_UNCLOSED_END ] ] ,
252- )
249+ if ( operations ?. length ) {
250+ resolveComponentsPathItemOperationSpec ( resultSpec , normalizedSpec , operations )
251+ }
253252}
254253
255- export const filterPathItemOperations = (
256- source : unknown ,
257- allowedMethods : string [ ] ,
258- ) : unknown => {
259- const httpMethods = new Set < string > ( Object . values ( OpenAPIV3 . HttpMethods ) as string [ ] )
260- const filteredSource : Record < string , unknown > = { ... ( source as Record < string , unknown > ) }
254+ export function resolveComponentsPathItemOperationSpec (
255+ sourceDocument : RestOperationData ,
256+ normalizedDocument : RestOperationData ,
257+ operations : OperationId [ ] ,
258+ ) : void {
259+ const { paths } = normalizedDocument
261260
262- for ( const key of Object . keys ( filteredSource ) ) {
263- if ( httpMethods . has ( key ) && ! allowedMethods . includes ( key ) ) {
264- delete filteredSource [ key ]
261+ for ( const path of Object . keys ( paths ) ) {
262+ const sourcePathItem = paths [ path ] as OpenAPIV3 . PathItemObject
263+ if ( ! isNonNullObject ( sourcePathItem ) ) {
264+ continue
265+ }
266+ const refs : string [ ] = hasInlineRefsFlag ( sourcePathItem ) ? sourcePathItem [ INLINE_REFS_FLAG ] : [ ]
267+ if ( refs . length === 0 ) {
268+ continue
269+ }
270+ const { jsonPath } = parseRef ( refs [ 0 ] )
271+ if ( ! jsonPath ) {
272+ continue
273+ }
274+
275+ const valueByPath = getValueByPath ( sourceDocument , jsonPath ) as OpenAPIV3 . PathItemObject
276+
277+ const operationIds : OpenAPIV3 . HttpMethods [ ] = ( Object . keys ( valueByPath ) as OpenAPIV3 . HttpMethods [ ] )
278+ . filter ( ( httpMethod ) => isValidHttpMethod ( httpMethod ) )
279+ . filter ( httpMethod => {
280+ const methodData = sourcePathItem [ httpMethod as OpenAPIV3 . HttpMethods ]
281+ if ( ! methodData ) return false
282+ const basePath = getOperationBasePath (
283+ methodData ?. servers ||
284+ sourcePathItem ?. servers ||
285+ [ ] ,
286+ )
287+ const operationId = getOperationId ( basePath , httpMethod , path )
288+ return operations . includes ( operationId )
289+ } )
290+
291+ if ( operationIds ?. length ) {
292+ const pathItemObject = {
293+ ...extractCommonPathItemProperties ( valueByPath ) ,
294+ ...operationIds . reduce < OpenAPIV3 . PathItemObject > ( ( pathItemObject : OpenAPIV3 . PathItemObject , operationId : OpenAPIV3 . HttpMethods ) => {
295+ const operationData = valueByPath [ operationId ]
296+ if ( operationData ) {
297+ pathItemObject [ operationId ] = { ...operationData }
298+ }
299+ return pathItemObject
300+ } , { } ) ,
301+ }
302+ setValueByPath ( sourceDocument , jsonPath , pathItemObject )
265303 }
266304 }
305+ }
267306
268- return filteredSource
307+ function isNonNullObject ( value : unknown ) : value is Record < string , unknown > {
308+ return typeof value === 'object' && value !== null
269309}
270310
271- export const getAllowedHttpOps = ( resultSpec : unknown , jsonPath : JsonPath ) : string [ ] => {
272- const resultComponents = getKeyValue ( resultSpec , ...jsonPath ) as unknown
273- if ( typeof resultComponents !== 'object' || resultComponents === null ) {
274- return [ ]
275- }
276- const httpMethods = new Set < string > ( Object . values ( OpenAPIV3 . HttpMethods ) as string [ ] )
277- return Object . keys ( resultComponents as Record < string , unknown > ) . filter ( key => httpMethods . has ( key ) )
311+ export const isComponentsSchemaRef = ( path : JsonPath ) : boolean => {
312+ return ! ! matchPaths (
313+ [ path ] ,
314+ [ [ OPEN_API_PROPERTY_COMPONENTS , OPEN_API_PROPERTY_SCHEMAS , PREDICATE_UNCLOSED_END ] ] ,
315+ )
278316}
279317
280318const isOperationPaths = ( paths : JsonPath [ ] ) : boolean => {
@@ -284,6 +322,10 @@ const isOperationPaths = (paths: JsonPath[]): boolean => {
284322 )
285323}
286324
325+ function hasInlineRefsFlag ( obj : unknown ) : obj is { [ INLINE_REFS_FLAG ] : string [ ] } {
326+ return typeof obj === 'object' && obj !== null && INLINE_REFS_FLAG in obj
327+ }
328+
287329// todo output of this method disrupts document normalization.
288330// origin symbols are not being transferred to the resulting spec.
289331// DO NOT pass output of this method to apiDiff
@@ -296,51 +338,27 @@ const createSingleOperationSpec = (
296338 security ?: OpenAPIV3 . SecurityRequirementObject [ ] ,
297339 securitySchemes ?: { [ p : string ] : OpenAPIV3 . ReferenceObject | OpenAPIV3 . SecuritySchemeObject } ,
298340) : TYPE . RestOperationData => {
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- }
341+ const pathData = document . paths [ path ] as OpenAPIV3 . PathItemObject
303342
304- const baseSpec = {
343+ const isContainsRef = ! ! pathData . $ref
344+ const refFlag = hasInlineRefsFlag ( pathData ) ? pathData [ INLINE_REFS_FLAG ] : false
345+ return {
305346 openapi : openapi ?? '3.0.0' ,
306347 ...takeIfDefined ( { servers } ) ,
307348 ...takeIfDefined ( { security } ) , // TODO: remove duplicates in security
349+ paths : {
350+ [ path ] : isContainsRef
351+ ? pathData
352+ : {
353+ ...extractCommonPathItemProperties ( pathData ) ,
354+ [ method ] : { ...pathData [ method ] } ,
355+ ...( refFlag ? { [ INLINE_REFS_FLAG ] : refFlag } : { } ) ,
356+ } ,
357+ } ,
308358 components : {
309359 ...takeIfDefined ( { securitySchemes } ) ,
310360 } ,
311361 }
312-
313- const ref = pathData . $ref
314- if ( ref ) {
315- const cleanedDocument = getValueByRefAndUpdate (
316- ref ,
317- document ,
318- ( pathItemObject : OpenAPIV3 . PathItemObject ) => ( {
319- ...extractCommonPathItemProperties ( pathItemObject ) ,
320- [ method ] : { ...pathItemObject [ method ] } ,
321- } ) )
322-
323- return {
324- ...baseSpec ,
325- paths : {
326- [ path ] : pathData ,
327- } ,
328- components : {
329- ...baseSpec . components ,
330- ...cleanedDocument . components ?? { } ,
331- } ,
332- }
333- }
334-
335- return {
336- ...baseSpec ,
337- paths : {
338- [ path ] : {
339- ...extractCommonPathItemProperties ( pathData ) ,
340- [ method ] : { ...pathData [ method ] } ,
341- } ,
342- } ,
343- }
344362}
345363
346364export const extractCommonPathItemProperties = (
@@ -352,3 +370,15 @@ export const extractCommonPathItemProperties = (
352370 ...takeIfDefined ( { parameters : pathData ?. parameters } ) ,
353371} )
354372
373+ function isValidHttpMethod ( method : string ) : method is OpenAPIV3 . HttpMethods {
374+ return ( Object . values ( OpenAPIV3 . HttpMethods ) as string [ ] ) . includes ( method )
375+ }
376+
377+ export function getOperationId (
378+ basePath : string ,
379+ key : string ,
380+ path : string ,
381+ ) : string {
382+ const operationPath = basePath + path
383+ return slugify ( `${ removeFirstSlash ( operationPath ) } -${ key } ` )
384+ }
0 commit comments