@@ -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 }  ) 
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