@@ -20,6 +20,7 @@ import {
2020 IGNORE_PATH_PARAM_UNIFIED_PLACEHOLDER ,
2121 isEmpty ,
2222 isOperationRemove ,
23+ isPathParamRenameDiff ,
2324 normalizePath ,
2425 removeComponents ,
2526 removeFirstSlash ,
@@ -60,7 +61,7 @@ import { calculateObjectHash } from '../../utils/hashes'
6061import { REST_API_TYPE } from './rest.consts'
6162import { OpenAPIV3 } from 'openapi-types'
6263import { extractServersDiffs , getOperationBasePath } from './rest.utils'
63- import { createOperationChange , getOperationTags , takeSubstringIf } from '../../components'
64+ import { createOperationChange , getOperationTags } from '../../components'
6465
6566export const compareDocuments = async ( apiType : OperationsApiType , operationsMap : Record < NormalizedOperationId , {
6667 previous ?: ResolvedOperation
@@ -69,17 +70,29 @@ export const compareDocuments = async (apiType: OperationsApiType, operationsMap
6970 operationChanges : OperationChanges [ ]
7071 tags : string [ ]
7172} > => {
72- const { rawDocumentResolver, previousVersion, currentVersion, previousPackageId, currentPackageId } = ctx
73+ const { rawDocumentResolver, previousVersion, currentVersion, previousPackageId, currentPackageId, currentGroup , previousGroup } = ctx
7374 const prevFile = prevDoc && await rawDocumentResolver ( previousVersion , previousPackageId , prevDoc . slug )
7475 const currFile = currDoc && await rawDocumentResolver ( currentVersion , currentPackageId , currDoc . slug )
7576 let prevDocData = prevFile && JSON . parse ( await prevFile . text ( ) )
7677 let currDocData = currFile && JSON . parse ( await currFile . text ( ) )
7778
79+ const isChangedOperations = prevDoc && currDoc
80+
81+ if ( prevDocData && previousGroup ) {
82+ // todo what's with components? why don't remove?
83+ prevDocData = createCopyWithCurrentGroupOperationsOnly ( prevDocData , previousGroup )
84+ }
85+
86+ if ( currDocData && currentGroup ) {
87+ // todo what's with components? why don't remove?
88+ currDocData = createCopyWithCurrentGroupOperationsOnly ( currDocData , currentGroup )
89+ }
90+
7891 if ( ! prevDocData && currDocData ) {
79- prevDocData = createCopyWithEmptyPath ( currDocData )
92+ prevDocData = createCopyWithEmptyPathItems ( currDocData )
8093 }
8194 if ( prevDocData && ! currDocData ) {
82- currDocData = createCopyWithEmptyPath ( prevDocData )
95+ currDocData = createCopyWithEmptyPathItems ( prevDocData )
8396 }
8497
8598 const { merged, diffs } = apiDiff (
@@ -99,13 +112,6 @@ export const compareDocuments = async (apiType: OperationsApiType, operationsMap
99112 return { operationChanges : [ ] , tags : [ ] }
100113 }
101114
102- // todo reclassify
103- // const olnyBreaking = diffs.filter((diff) => diff.type === breaking)
104- // if (olnyBreaking.length > 0 && previous?.operationId) {
105- // await reclassifyBreakingChanges(previous.operationId, diffResult.merged, olnyBreaking, ctx)
106- // }
107-
108- const { currentGroup, previousGroup } = ctx . config
109115 const currGroupSlug = slugify ( removeFirstSlash ( currentGroup || '' ) )
110116 const prevGroupSlug = slugify ( removeFirstSlash ( previousGroup || '' ) )
111117
@@ -127,40 +133,47 @@ export const compareDocuments = async (apiType: OperationsApiType, operationsMap
127133 const methodData = pathData [ inferredMethod ]
128134 const basePath = getOperationBasePath ( methodData ?. servers || pathData ?. servers || merged . servers || [ ] )
129135 const operationPath = basePath + path
130- const operationId = slugify ( `${ removeFirstSlash ( operationPath ) } -${ inferredMethod } ` )
136+ const operationId = slugify ( `${ operationPath } -${ inferredMethod } ` )
131137 const normalizedOperationId = slugify ( `${ normalizePath ( basePath + path ) } -${ inferredMethod } ` , [ ] , IGNORE_PATH_PARAM_UNIFIED_PLACEHOLDER )
132- // todo what's with prevslug? which tests affected? which slug to slice prev or curr?
133- const qwe = takeSubstringIf ( ! ! currGroupSlug , normalizedOperationId , currGroupSlug . length )
134138
135- const { current, previous } = operationsMap [ qwe ] ?? operationsMap [ operationId ] ?? { }
139+ const { current, previous } = operationsMap [ normalizedOperationId ] ?? operationsMap [ operationId ] ?? { }
136140
137141 let operationDiffs : Diff [ ] = [ ]
138- if ( current && previous ) {
142+ if ( current && previous && isChangedOperations ) {
139143 operationDiffs = [
140144 ...( methodData as WithAggregatedDiffs < OpenAPIV3 . OperationObject > ) [ DIFFS_AGGREGATED_META_KEY ] ,
141145 // todo what about security? add test
142146 ...extractServersDiffs ( merged ) ,
143147 ]
144148
145- const pathParamRenameDiff = ( merged . paths as WithDiffMetaRecord < OpenAPIV3 . PathsObject > ) [ DIFF_META_KEY ] ?. [ path ]
146- pathParamRenameDiff && operationDiffs . push ( pathParamRenameDiff )
147- } else if ( current || previous ) {
149+ const diff = ( merged . paths as WithDiffMetaRecord < OpenAPIV3 . PathsObject > ) [ DIFF_META_KEY ] ?. [ path ]
150+ if ( diff && isPathParamRenameDiff ( diff ) ) {
151+ // ignore removed and added operations, they'll be handled in a separate docs comparison???
152+ operationDiffs . push ( diff )
153+ }
154+ }
155+ if ( ( ! ! current !== ! ! previous ) && ! isChangedOperations ) {
148156 const operationDiff = ( merged . paths [ path ] as WithDiffMetaRecord < OpenAPIV3 . PathsObject > ) [ DIFF_META_KEY ] ?. [ inferredMethod ]
149157 if ( ! operationDiff ) {
150158 // ignore removed and added operations, they'll be handled in a separate docs comparison
151159 continue
152160 }
153- const deprecatedInVersionsCount = previousVersionDeprecations ?. operations . find ( ( operation ) => operation . operationId === operationId ) ?. deprecatedInPreviousVersions ?. length ?? 0
154- if ( isOperationRemove ( operationDiff ) && deprecatedInVersionsCount > 1 ) {
155- operationDiff . type = risky
156- }
161+ // const deprecatedInVersionsCount = previousVersionDeprecations?.operations.find((operation) => operation.operationId === operationId)?.deprecatedInPreviousVersions?.length ?? 0
162+ // if (isOperationRemove(operationDiff) && deprecatedInVersionsCount > 1) {
163+ // operationDiff.type = risky
164+ // }
157165 operationDiffs . push ( operationDiff )
158166 }
159167
160168 if ( isEmpty ( operationDiffs ) ) {
161169 continue
162170 }
163171
172+ const onlyBreaking = operationDiffs . filter ( ( diff ) => diff . type === breaking )
173+ if ( onlyBreaking . length > 0 && previous ?. operationId ) {
174+ await reclassifyBreakingChanges ( previous . operationId , merged , onlyBreaking , ctx )
175+ }
176+
164177 // todo operationDiffs can be [undefined] in 'type error must not appear during build'
165178 changedOperations . push ( createOperationChange ( apiType , operationDiffs , previous , current , currGroupSlug , prevGroupSlug , currentGroup , previousGroup ) )
166179 getOperationTags ( current ?? previous ) . forEach ( tag => tags . add ( tag ) )
@@ -176,11 +189,11 @@ export const compareRestOperationsData = async (current: VersionRestOperation |
176189 let previousOperation = removeComponents ( previous ?. data )
177190 let currentOperation = removeComponents ( current ?. data )
178191 if ( ! previousOperation && currentOperation ) {
179- previousOperation = createCopyWithEmptyPath ( currentOperation as RestOperationData )
192+ previousOperation = createCopyWithEmptyPathItems ( currentOperation as RestOperationData )
180193 }
181194
182195 if ( previousOperation && ! currentOperation ) {
183- currentOperation = createCopyWithEmptyPath ( previousOperation as RestOperationData )
196+ currentOperation = createCopyWithEmptyPathItems ( previousOperation as RestOperationData )
184197 }
185198
186199 const diffResult = apiDiff (
@@ -285,13 +298,30 @@ async function reclassifyBreakingChanges(
285298 }
286299}
287300
288- export function createCopyWithEmptyPath ( template : RestOperationData ) : RestOperationData {
289- // eslint-disable-next-line @typescript-eslint/no-unused-vars
301+ export function createCopyWithEmptyPathItems ( template : RestOperationData ) : RestOperationData {
302+ const { paths, ...rest } = template
303+
304+ return {
305+ paths : {
306+ ...Object . fromEntries ( Object . keys ( paths ) . map ( key => [ key , { } ] ) ) ,
307+ } ,
308+ ...rest ,
309+ }
310+ }
311+
312+ export function createCopyWithCurrentGroupOperationsOnly ( template : RestOperationData , group : string ) : RestOperationData {
290313 const { paths, ...rest } = template
314+ const groupWithoutLeadingSlash = removeFirstSlash ( group )
291315
292316 return {
293317 paths : {
294- ...Object . fromEntries ( Object . keys ( template . paths ) . map ( key => [ key , { } ] ) ) ,
318+ ...Object . fromEntries (
319+ Object . entries ( paths )
320+ . filter ( ( [ key ] ) => removeFirstSlash ( key ) . startsWith ( groupWithoutLeadingSlash ) )
321+ // remove prefix group for correct path mapping in apiDiff
322+ // todo support the most common case when a group is in servers instead of hardcoded in path, add a test
323+ . map ( ( [ key , value ] ) => [ removeFirstSlash ( key ) . substring ( groupWithoutLeadingSlash . length ) , value ] ) ,
324+ ) ,
295325 } ,
296326 ...rest ,
297327 }
0 commit comments