@@ -19,38 +19,40 @@ 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  { 
2323  BuildConfig , 
2424  CrawlRule , 
2525  DeprecateItem , 
2626  NotificationMessage , 
2727  OperationCrawlState , 
28+   OperationId , 
2829  SearchScopes , 
2930}  from  '../../types' 
3031import  { 
3132  buildSearchScope , 
3233  capitalize , 
3334  getKeyValue , 
3435  getSplittedVersionKey , 
35-   getValueByRefAndUpdate , 
3636  isDeprecatedOperationItem , 
37+   isObject , 
3738  isOperationDeprecated , 
3839  normalizePath , 
3940  rawToApiKind , 
41+   removeFirstSlash , 
4042  setValueByPath , 
43+   slugify , 
4144  takeIf , 
4245  takeIfDefined , 
4346}  from  '../../utils' 
4447import  {  API_KIND ,  INLINE_REFS_FLAG ,  ORIGINS_SYMBOL ,  VERSION_STATUS  }  from  '../../consts' 
45- import  {  getCustomTags ,  resolveApiAudience  }  from  './rest.utils' 
48+ import  {  getCustomTags ,  getOperationBasePath ,   resolveApiAudience  }  from  './rest.utils' 
4649import  {  DebugPerformanceContext ,  syncDebugPerformance  }  from  '../../utils/logs' 
4750import  { 
4851  calculateDeprecatedItems , 
4952  grepValue , 
5053  JSON_SCHEMA_PROPERTY_DEPRECATED , 
5154  matchPaths , 
5255  OPEN_API_PROPERTY_COMPONENTS , 
53-   OPEN_API_PROPERTY_PATH_ITEMS , 
5456  OPEN_API_PROPERTY_PATHS , 
5557  OPEN_API_PROPERTY_SCHEMAS , 
5658  parseRef , 
@@ -61,6 +63,7 @@ import {
6163}  from  '@netcracker/qubership-apihub-api-unifier' 
6264import  {  calculateObjectHash  }  from  '../../utils/hashes' 
6365import  {  calculateTolerantHash  }  from  '../../components/deprecated' 
66+ import  {  getValueByPath  }  from  '../../utils/path' 
6467
6568export  const  buildRestOperation  =  ( 
6669  operationId : string , 
@@ -145,7 +148,8 @@ export const buildRestOperation = (
145148      security , 
146149      components ?. securitySchemes , 
147150    ) 
148-     calculateSpecRefs ( document . data ,  refsOnlySingleOperationSpec ,  specWithSingleOperation ,  models ,  componentsHashMap ) 
151+     calculateSpecRefs ( document . data ,  refsOnlySingleOperationSpec ,  specWithSingleOperation ,  [ operationId ] ,  models ,  componentsHashMap ) 
152+     createSinglePathItemOperationSpec ( specWithSingleOperation  as  OpenAPIV3 . Document ,  refsOnlySingleOperationSpec  as  OpenAPIV3 . Document ,  [ operationId ] ) 
149153    const  dataHash  =  calculateObjectHash ( specWithSingleOperation ) 
150154    return  [ specWithSingleOperation ,  dataHash ] 
151155  } ,  debugCtx ) 
@@ -182,7 +186,7 @@ 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  =  ( sourceDocument : unknown ,  normalizedSpec : unknown ,  resultSpec : unknown ,  operations :  OperationId [ ] ,   models ?: Record < string ,  string > ,  componentsHashMap ?: Map < string ,  string > ) : void =>  { 
186190  const  handledObjects  =  new  Set < unknown > ( ) 
187191  const  inlineRefs  =  new  Set < string > ( ) 
188192  syncCrawl ( 
@@ -214,67 +218,70 @@ export const calculateSpecRefs = (sourceDocument: unknown, normalizedSpec: unkno
214218      return 
215219    } 
216220    const  componentName  =  matchResult . grepValues [ grepKey ] . toString ( ) 
217-     let  sourceComponents  =  getKeyValue ( sourceDocument ,  ...matchResult . path ) 
218-     if  ( ! sourceComponents   ||   typeof   sourceComponents   !==   'object' )  { 
221+     let  component :  any  =  getKeyValue ( sourceDocument ,  ...matchResult . path ) 
222+     if  ( ! component )  { 
219223      return 
220224    } 
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-       } 
225+     if  ( isObject ( component ) )  { 
226+       component  =  {  ...component  } 
227227    } 
228228    if  ( models  &&  ! models [ componentName ]  &&  isComponentsSchemaRef ( matchResult . path ) )  { 
229-       const   existingHash  =  componentsHashMap ?. get ( componentName ) 
230-       if  ( existingHash )  { 
231-         models [ componentName ]  =  existingHash 
229+       let   componentHash  =  componentsHashMap ?. get ( componentName ) 
230+       if  ( componentHash )  { 
231+         models [ componentName ]  =  componentHash 
232232      }  else  { 
233-         const   componentHashCalculated   =  calculateObjectHash ( sourceComponents   as   object ) 
234-         componentsHashMap ?. set ( componentName ,  componentHashCalculated ) 
235-         models [ componentName ]  =  componentHashCalculated 
233+         componentHash   =  calculateObjectHash ( component ) 
234+         componentsHashMap ?. set ( componentName ,  componentHash ) 
235+         models [ componentName ]  =  componentHash 
236236      } 
237237    } 
238-     setValueByPath ( resultSpec ,  matchResult . path ,  sourceComponents ) 
238+ 
239+     setValueByPath ( resultSpec ,  matchResult . path ,  component ) 
239240  } ) 
240241} 
241242
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-   ) 
253- } 
243+ export  function  createSinglePathItemOperationSpec ( sourceDocument : OpenAPIV3 . Document ,  normalizedDocument : OpenAPIV3 . Document ,  operations : OperationId [ ] ) : void { 
244+   const  {  paths }  =  normalizedDocument 
254245
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 > )  } 
246+   for  ( const  path  of  Object . keys ( paths ) )  { 
247+     const  sourcePathItem  =  paths [ path ] 
261248
262-   for  ( const  key  of  Object . keys ( filteredSource ) )  { 
263-     if  ( httpMethods . has ( key )  &&  ! allowedMethods . includes ( key ) )  { 
264-       delete  filteredSource [ key ] 
249+     const  refs  =  ( sourcePathItem  as  any ) [ INLINE_REFS_FLAG ] 
250+     if  ( ! isNonNullObject ( sourcePathItem )  ||  ! refs  ||  refs . length  ===  0 )  { 
251+       continue 
252+     } 
253+     const  richReference  =  parseRef ( refs [ 0 ] ) 
254+     const  valueByPath  =  getValueByPath ( sourceDocument ,  richReference . jsonPath ) 
255+     for  ( const  method  of  Object . keys ( valueByPath ) )  { 
256+       const  httpMethod  =  method  as  OpenAPIV3 . HttpMethods 
257+       if  ( ! isValidHttpMethod ( httpMethod ) )  continue 
258+ 
259+       const  methodData  =  sourcePathItem [ httpMethod ] 
260+       const  basePath  =  getOperationBasePath ( 
261+         methodData ?. servers  || 
262+         sourcePathItem ?. servers  || 
263+         [ ] , 
264+       ) 
265+ 
266+       const  operationPath  =  basePath  +  path 
267+       const  operationId  =  slugify ( `${ removeFirstSlash ( operationPath ) } ${ method }  ) 
268+ 
269+       if  ( ! operations . includes ( operationId ) )  { 
270+         delete  valueByPath [ method ] 
271+       } 
265272    } 
266273  } 
274+ } 
267275
268-   return  filteredSource 
276+ function  isNonNullObject ( value : unknown ) : value  is Record < string ,  unknown >  { 
277+   return  typeof  value  ===  'object'  &&  value  !==  null 
269278} 
270279
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 ) ) 
280+ export  const  isComponentsSchemaRef  =  ( path : JsonPath ) : boolean  =>  { 
281+   return  ! ! matchPaths ( 
282+     [ path ] , 
283+     [ [ OPEN_API_PROPERTY_COMPONENTS ,  OPEN_API_PROPERTY_SCHEMAS ,  PREDICATE_UNCLOSED_END ] ] , 
284+   ) 
278285} 
279286
280287const  isOperationPaths  =  ( paths : JsonPath [ ] ) : boolean  =>  { 
@@ -296,51 +303,27 @@ const createSingleOperationSpec = (
296303  security ?: OpenAPIV3 . SecurityRequirementObject [ ] , 
297304  securitySchemes ?: {  [ p : string ] : OpenAPIV3 . ReferenceObject  |  OpenAPIV3 . SecuritySchemeObject  } , 
298305) : TYPE . RestOperationData  =>  { 
299-   const  pathData  =  document . paths [ path ]  as  OpenAPIV3 . PathItemObject  |  undefined 
300-   if  ( ! pathData )  { 
301-     throw  new  Error ( `Path "${ path }  ) 
302-   } 
306+   const  pathData  =  document . paths [ path ]  as  OpenAPIV3 . PathItemObject 
303307
304-   const  baseSpec  =  { 
308+   const  ref  =  pathData . $ref 
309+   const  refFlag  =  ( pathData  as  any ) [ INLINE_REFS_FLAG ] 
310+   return  { 
305311    openapi : openapi  ??  '3.0.0' , 
306312    ...takeIfDefined ( {  servers } ) , 
307313    ...takeIfDefined ( {  security } ) ,  // TODO: remove duplicates in security 
314+     paths : { 
315+       [ path ] : ref 
316+         ? pathData 
317+         : { 
318+           ...extractCommonPathItemProperties ( pathData ) , 
319+           [ method ] : {  ...pathData [ method ]  } , 
320+           ...( refFlag  ? {  [ INLINE_REFS_FLAG ] : refFlag  }  : { } ) , 
321+         } , 
322+     } , 
308323    components : { 
309324      ...takeIfDefined ( {  securitySchemes } ) , 
310325    } , 
311326  } 
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-   } 
344327} 
345328
346329export  const  extractCommonPathItemProperties  =  ( 
@@ -352,3 +335,6 @@ export const extractCommonPathItemProperties = (
352335  ...takeIfDefined ( {  parameters : pathData ?. parameters  } ) , 
353336} ) 
354337
338+ function  isValidHttpMethod ( method : string ) : method  is OpenAPIV3 . HttpMethods  { 
339+   return  ( Object . values ( OpenAPIV3 . HttpMethods )  as  string [ ] ) . includes ( method ) 
340+ } 
0 commit comments