Skip to content

Commit 6164b3e

Browse files
committed
chore: merge branch 'develop' into feature/asyncapi-basic-e2e
2 parents 9944d90 + 8b5f152 commit 6164b3e

File tree

38 files changed

+3786
-1877
lines changed

38 files changed

+3786
-1877
lines changed

jest.config.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,8 @@ module.exports = {
2525
// "^@netcracker/qubership-apihub-graphapi$":'<rootDir>/../qubership-apihub-graphapi/src',
2626
// "^@netcracker/qubership-apihub-compatibility-suites$":'<rootDir>/../apihub-compatibility-suites/generation/suite-service',
2727
// },
28-
setupFilesAfterEnv: ['jest-extended/all'],
28+
setupFilesAfterEnv: [
29+
'jest-extended/all',
30+
'<rootDir>/test/setup/jest-wrappers.ts',
31+
],
2932
}

package-lock.json

Lines changed: 2518 additions & 1617 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
],
1010
"exports": {
1111
".": {
12+
"types": "./dist/index.d.ts",
1213
"import": "./dist/index.es.js",
13-
"require": "./dist/index.cjs.js",
14-
"types": "./dist/index.d.ts"
14+
"require": "./dist/index.cjs.js"
1515
}
1616
},
1717
"scripts": {
@@ -22,41 +22,41 @@
2222
"development:link": "npm link && npm link @netcracker/qubership-apihub-json-crawl && npm link @netcracker/qubership-apihub-graphapi && npm link @netcracker/qubership-apihub-api-unifier && npm link @netcracker/qubership-apihub-compatibility-suites",
2323
"development:unlink": "npm unlink && npm unlink @netcracker/qubership-apihub-json-crawl && npm unlink @netcracker/qubership-apihub-graphapi && npm unlink @netcracker/qubership-apihub-api-unifier && npm unlink @netcracker/qubership-apihub-compatibility-suites",
2424
"test": "jest --maxWorkers 3 --verbose",
25-
"test:compatibility-suites": "jest --findRelatedTests --detectOpenHandles test/compatibility-suites/*",
26-
"test:human-readble": "jest --findRelatedTests --detectOpenHandles test/human-readable/*",
25+
"test:compatibility-suites": "jest --detectOpenHandles test/compatibility-suites",
26+
"test:human-readable": "jest --detectOpenHandles test/human-readable",
2727
"update-lock-file": "update-lock-file @netcracker"
2828
},
2929
"dependencies": {
3030
"@netcracker/qubership-apihub-api-unifier": "feature-asyncapi-basic-e2e",
3131
"@netcracker/qubership-apihub-json-crawl": "dev",
32-
"fast-equals": "4.0.3"
32+
"fast-equals": "6.0.0"
3333
},
3434
"devDependencies": {
3535
"@netcracker/qubership-apihub-compatibility-suites": "dev",
3636
"@netcracker/qubership-apihub-graphapi": "dev",
3737
"@netcracker/qubership-apihub-npm-gitflow": "3.1.0",
38-
"@types/jest": "29.5.11",
39-
"@types/node": "20.11.6",
40-
"@typescript-eslint/eslint-plugin": "6.13.2",
41-
"@typescript-eslint/parser": "6.13.2",
42-
"eslint": "8.55.0",
43-
"eslint-plugin-sort-exports": "0.8.0",
44-
"jest": "29.7.0",
45-
"jest-extended": "^4.0.2",
38+
"@types/jest": "30.0.0",
39+
"@types/js-yaml": "4.0.9",
40+
"@types/node": "25.0.3",
41+
"@typescript-eslint/eslint-plugin": "7.18.0",
42+
"@typescript-eslint/parser": "7.18.0",
43+
"eslint": "8.57.1",
44+
"eslint-plugin-sort-exports": "0.9.1",
45+
"fast-json-patch": "3.1.1",
46+
"graphql": "16.12.0",
47+
"jest": "30.2.0",
48+
"jest-extended": "7.0.0",
49+
"js-yaml": "4.1.1",
4650
"openapi-types": "12.1.3",
47-
"@types/js-yaml": "^4.0.5",
48-
"fast-json-patch": "^3.1.1",
49-
"graphql": "16.9.0",
50-
"js-yaml": "^4.1.0",
51-
"rimraf": "^5.0.5",
52-
"ts-loader": "^8.4.0",
53-
"ts-node": "^10.9.2",
54-
"tslint": "^6.1.3",
55-
"ts-jest": "29.1.1",
56-
"typescript": "5.3.3",
57-
"vite": "5.2.6",
58-
"vite-plugin-dts": "3.6.4",
59-
"vite-plugin-singlefile": "2.0.1"
51+
"rimraf": "6.1.2",
52+
"ts-jest": "29.4.6",
53+
"ts-loader": "9.5.4",
54+
"ts-node": "10.9.2",
55+
"tslint": "6.1.3",
56+
"typescript": "5.9.3",
57+
"vite": "7.3.1",
58+
"vite-plugin-dts": "4.5.4",
59+
"vite-plugin-singlefile": "2.3.0"
6060
},
6161
"eslintConfig": {
6262
"extends": ".eslintrc.json"

src/core/compare.ts

Lines changed: 68 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import { deepEqual } from 'fast-equals'
1616
import {
1717
AdapterContext,
1818
AdapterResolver,
19+
API_COMPATIBILITY_KIND_BACKWARD_COMPATIBLE,
20+
ApiCompatibilityKind,
1921
CompareContext,
2022
CompareResult,
2123
CompareRule,
@@ -75,6 +77,7 @@ export const createContext = (data: ContextInput, options: InternalCompareOption
7577
rules,
7678
compareScope,
7779
parentContext,
80+
apiCompatibilityScope,
7881
} = data
7982
return {
8083
parentContext: parentContext,
@@ -84,6 +87,7 @@ export const createContext = (data: ContextInput, options: InternalCompareOption
8487
mergeKey,
8588
rules,
8689
options,
90+
apiCompatibilityScope: apiCompatibilityScope,
8791
}
8892
}
8993

@@ -92,6 +96,7 @@ export const createChildContext = (
9296
mergedKey: PropertyKey,
9397
beforeChildKey: PropertyKey | undefined,
9498
afterChildKey: PropertyKey | undefined,
99+
apiCompatibilityScope: ApiCompatibilityKind = ctx.apiCompatibilityScope,
95100
): CompareContext => {
96101
const { before, after, rules, options, scope } = ctx
97102
let beforeContext: NodeContext
@@ -133,6 +138,7 @@ export const createChildContext = (
133138
) ?? {},
134139
options,
135140
scope: scope,
141+
apiCompatibilityScope: apiCompatibilityScope,
136142
}
137143
}
138144

@@ -156,7 +162,19 @@ const cleanUpRecursive = (ctx: NodeContext): NodeContext => {
156162
}
157163

158164
export const getOrCreateChildDiffAdd = (diffUniquenessCache: EvaluationCacheService, childCtx: CompareContext) => {
159-
const diff = diffUniquenessCache.cacheEvaluationResultByFootprint<[unknown, string, CompareScope, typeof DiffAction.add], DiffAdd>([childCtx.after.value, buildPathsIdentifier(childCtx.after.declarativePaths), childCtx.scope, DiffAction.add], () => {
165+
const diff = diffUniquenessCache.cacheEvaluationResultByFootprint<[
166+
unknown,
167+
string,
168+
CompareScope,
169+
typeof DiffAction.add,
170+
ApiCompatibilityKind
171+
], DiffAdd>([
172+
childCtx.after.value,
173+
buildPathsIdentifier(childCtx.after.declarativePaths),
174+
childCtx.scope,
175+
DiffAction.add,
176+
childCtx.apiCompatibilityScope,
177+
], () => {
160178
return diffFactory.added(childCtx)
161179
}, {} as DiffAdd, (result, guard) => {
162180
Object.assign(guard, result)
@@ -167,7 +185,19 @@ export const getOrCreateChildDiffAdd = (diffUniquenessCache: EvaluationCacheServ
167185
}
168186

169187
export const getOrCreateChildDiffRemove = (diffUniquenessCache: EvaluationCacheService, childCtx: CompareContext) => {
170-
const diff = diffUniquenessCache.cacheEvaluationResultByFootprint<[unknown, string, CompareScope, typeof DiffAction.remove], DiffRemove>([childCtx.before.value, buildPathsIdentifier(childCtx.before.declarativePaths), childCtx.scope, DiffAction.remove], () => {
188+
const diff = diffUniquenessCache.cacheEvaluationResultByFootprint<[
189+
unknown,
190+
string,
191+
CompareScope,
192+
typeof DiffAction.remove,
193+
ApiCompatibilityKind
194+
], DiffRemove>([
195+
childCtx.before.value,
196+
buildPathsIdentifier(childCtx.before.declarativePaths),
197+
childCtx.scope,
198+
DiffAction.remove,
199+
childCtx.apiCompatibilityScope,
200+
], () => {
171201
return diffFactory.removed(childCtx)
172202
}, {} as DiffRemove, (result, guard) => {
173203
Object.assign(guard, result)
@@ -201,9 +231,8 @@ const adaptValues = (beforeJso: JsonNode, beforeKey: PropertyKey, afterJso: Json
201231
})
202232
return [beforeValueAdapted, afterValueAdapted]
203233
}
204-
205234
const useMergeFactory = (onDiff: DiffCallback, options: InternalCompareOptions): SyncCrawlHook<MergeState, CompareRule> => {
206-
const { metaKey } = options
235+
const { metaKey, apiCompatibilityScopeFunction } = options
207236
const diffs: Set<Diff> = new Set()
208237
const addDiff: (diff: Diff) => void = (diff) => {
209238
const oldSize = diffs.size
@@ -218,6 +247,7 @@ const useMergeFactory = (onDiff: DiffCallback, options: InternalCompareOptions):
218247
compare,
219248
mapping,
220249
ignoreKeyDifference,
250+
syntheticDiffs: mappingSyntheticDiffsPostProcessor,
221251
newCompareScope,
222252
} = rules
223253
const {
@@ -229,6 +259,7 @@ const useMergeFactory = (onDiff: DiffCallback, options: InternalCompareOptions):
229259
diffUniquenessCache,
230260
createdMergedJso,
231261
compareScope,
262+
apiCompatibilityScope: parentApiCompatibilityScope,
232263
} = state
233264

234265
if (typeof unsafeKey === 'symbol') {
@@ -252,6 +283,8 @@ const useMergeFactory = (onDiff: DiffCallback, options: InternalCompareOptions):
252283
afterValueAdapted,
253284
] = adaptValues(beforeJso, beforeKey, afterJso, afterKey, adapter, options)
254285

286+
const computedApiCompatibilityScope = apiCompatibilityScopeFunction?.(crawlContext.path, beforeValueAdapted, afterValueAdapted) ?? parentApiCompatibilityScope
287+
255288
const ctx = createContext({
256289
...state,
257290
beforeValue: beforeValueAdapted,
@@ -261,12 +294,27 @@ const useMergeFactory = (onDiff: DiffCallback, options: InternalCompareOptions):
261294
mergeKey,
262295
rules,
263296
compareScope: newCompareScope ?? compareScope,
297+
apiCompatibilityScope: computedApiCompatibilityScope,
264298
}, options)
265299

266300
const beforeDeclarativePathsId = buildPathsIdentifier(cleanUpRecursive(ctx.before).declarativePaths)
267301
const afterDeclarativePathsId = buildPathsIdentifier(cleanUpRecursive(ctx.after).declarativePaths)
268302

269-
const reuseResult: ReusableMergeResult = mergedJsoCache.cacheEvaluationResultByFootprint<[typeof ctx.before.value, typeof ctx.after.value, typeof beforeDeclarativePathsId, typeof afterDeclarativePathsId, CompareScope], ReusableMergeResult>([ctx.before.value, ctx.after.value, beforeDeclarativePathsId, afterDeclarativePathsId, ctx.scope], ([beforeValue, afterValue]) => {
303+
const reuseResult: ReusableMergeResult = mergedJsoCache.cacheEvaluationResultByFootprint<[
304+
typeof ctx.before.value,
305+
typeof ctx.after.value,
306+
typeof beforeDeclarativePathsId,
307+
typeof afterDeclarativePathsId,
308+
CompareScope,
309+
ApiCompatibilityKind
310+
], ReusableMergeResult>([
311+
ctx.before.value,
312+
ctx.after.value,
313+
beforeDeclarativePathsId,
314+
afterDeclarativePathsId,
315+
ctx.scope,
316+
computedApiCompatibilityScope,
317+
], ([beforeValue, afterValue]) => {
270318
if (!ignoreKeyDifference && beforeKey !== afterKey) {
271319
const diffEntry = createDiffEntry(ctx, diffFactory.renamed(ctx))
272320
addDiff(diffEntry.diff)
@@ -291,11 +339,17 @@ const useMergeFactory = (onDiff: DiffCallback, options: InternalCompareOptions):
291339
if (isObject(beforeValue) && isObject(afterValue)) {
292340
const mergedJsoValue: JsonNode = isArray(beforeValue) ? [] as JsonNode<number> : {} as JsonNode<string | symbol>
293341
const mapKeys = mapping ?? (isArray(beforeValue) ? arrayMappingResolver : objectMappingResolver)
342+
const mappingData = mapKeys(beforeValue as any, afterValue as any, ctx)
343+
344+
// Adding synthetic diffs if necessary
345+
mappingSyntheticDiffsPostProcessor?.(mappingData, beforeValue, afterValue)
346+
294347
const {
295348
added: addedKeys,
296349
removed: removedKeys,
297350
mapped: mappedKeys,
298-
} = mapKeys(beforeValue as any, afterValue as any, ctx)
351+
} = mappingData
352+
299353
const jsoDiffEntries: DiffEntry<Diff>[] = []
300354
const keyToRemove = removedKeys
301355
.filter(key => !isDefaultValue(beforeValue, key, options.defaultsFlag))
@@ -310,13 +364,15 @@ const useMergeFactory = (onDiff: DiffCallback, options: InternalCompareOptions):
310364
once = true
311365

312366
keyToRemove.forEach((keyToBefore) => {
313-
const childCtx = createChildContext(ctx, keyToBefore, keyToBefore, undefined)
367+
const removalBwc = apiCompatibilityScopeFunction?.([...crawlContext.path, keyToBefore], beforeValue[keyToBefore]) || computedApiCompatibilityScope
368+
const childCtx = createChildContext(ctx, keyToBefore, keyToBefore, undefined, removalBwc)
314369
jsoDiffEntries.push(getOrCreateChildDiffRemove(diffUniquenessCache, childCtx))
315370
})
316371

317372
keysToAdd.forEach((keyInAfter) => {
373+
const additionBwc = apiCompatibilityScopeFunction?.([...crawlContext.path, keyInAfter], undefined, afterJso[keyInAfter]) || computedApiCompatibilityScope
318374
const keyInMerge = isArray(mergedJsoValue) ? mergedJsoValue.length : keyInAfter
319-
const childCtx = createChildContext(ctx, keyInMerge, undefined, keyInAfter)
375+
const childCtx = createChildContext(ctx, keyInMerge, undefined, keyInAfter, additionBwc)
320376
jsoDiffEntries.push(getOrCreateChildDiffAdd(diffUniquenessCache, childCtx))
321377
mergedJsoValue[keyInMerge] = afterValue[keyInAfter]
322378
})
@@ -363,6 +419,7 @@ const useMergeFactory = (onDiff: DiffCallback, options: InternalCompareOptions):
363419
afterJso: afterValueAdapted as JsonNode/*safe cause it only happens for object*/,
364420
mergedJso: mergedValue,
365421
compareScope: newCompareScope ?? compareScope,
422+
apiCompatibilityScope: computedApiCompatibilityScope,
366423
}
367424
return { value: reuseResult.nextValue, state: childState, exitHook: reuseResult.exitHook }
368425
} else {
@@ -532,6 +589,8 @@ const compareInternal = (before: unknown, after: unknown, onDiff: DiffCallback,
532589
const beforeRootJso = root.before
533590
const afterRootJso = root.after
534591

592+
const apiCompatibilityScope = options?.apiCompatibilityScopeFunction?.() || API_COMPATIBILITY_KIND_BACKWARD_COMPATIBLE
593+
535594
if (!isObject(beforeRootJso) || !isObject(afterRootJso)) {
536595
// TODO
537596
throw new Error('Not ready to compare primitive')
@@ -548,6 +607,7 @@ const compareInternal = (before: unknown, after: unknown, onDiff: DiffCallback,
548607
diffUniquenessCache: options.diffUniquenessCache,
549608
createdMergedJso: options.createdMergedJso,
550609
compareScope: options.compareScope,
610+
apiCompatibilityScope: apiCompatibilityScope,
551611
}
552612
syncCrawl<MergeState, CompareRule>(before, [hook], { state: rootState, rules: options.rules })
553613
return root.merged[JSO_ROOT]

src/core/diff.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,20 @@
11
import { JsonPath } from '@netcracker/qubership-apihub-json-crawl'
22

3-
import { CompareContext, Diff, DiffAdd, DiffRemove, DiffReplace, DiffRename, DiffEntry, DiffFactory, DiffMetaRecord, NodeContext } from '../types'
4-
import { allUnclassified, DiffAction, unclassified } from './constants'
3+
import {
4+
CompareContext,
5+
Diff,
6+
DiffAdd,
7+
DiffEntry,
8+
DiffFactory,
9+
DiffMetaRecord,
10+
DiffRemove,
11+
DiffRename,
12+
DiffReplace,
13+
DiffType,
14+
NodeContext,
15+
API_COMPATIBILITY_KIND_NOT_BACKWARD_COMPATIBLE,
16+
} from '../types'
17+
import { allUnclassified, breaking, DiffAction, risky, unclassified } from './constants'
518
import { getKeyValue, isFunc } from '../utils'
619
import { calculateDefaultDiffDescription } from './description'
720

@@ -18,7 +31,8 @@ export const createDiff = <D extends Diff>(diff: Omit<D, 'type'>, ctx: CompareCo
1831
const changeType = classifier[index]
1932

2033
try {
21-
mutableDiffCopy.type = isFunc(changeType) ? changeType(ctx) : changeType
34+
const type = isFunc(changeType) ? changeType(ctx) : changeType
35+
mutableDiffCopy.type = reclassifyBreakingToRisky(type, ctx)
2236
} catch (error) {
2337
ctx.options.onCreateDiffError?.(`Unable to find diff type. ${error instanceof Error ? error.message : ''}`, mutableDiffCopy, ctx)
2438
}
@@ -31,6 +45,10 @@ export const createDiff = <D extends Diff>(diff: Omit<D, 'type'>, ctx: CompareCo
3145
return mutableDiffCopy
3246
}
3347

48+
export const reclassifyBreakingToRisky = (type: DiffType, ctx: CompareContext): DiffType => {
49+
return type === breaking && ctx.apiCompatibilityScope === API_COMPATIBILITY_KIND_NOT_BACKWARD_COMPATIBLE ? risky : type
50+
}
51+
3452
export function createDiffEntry(ctx: CompareContext, diff: Diff): DiffEntry<Diff> {
3553
return ({
3654
propertyKey: ctx.mergeKey,

src/core/rules.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ export const transformClassifyRule = ([add, remove, replace, reverseAdd, reverse
8080
transformedRule(remove, DiffAction.remove),
8181
transformedRule(replace, DiffAction.replace),
8282
]
83-
8483
}
8584

8685
export const breakingIf = (v: boolean): DiffType => (v ? breaking : nonBreaking)

src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export { COMPARE_MODE_DEFAULT, COMPARE_MODE_OPERATION } from './types'
1+
export { COMPARE_MODE_DEFAULT, COMPARE_MODE_OPERATION, API_COMPATIBILITY_KIND_BACKWARD_COMPATIBLE, API_COMPATIBILITY_KIND_NOT_BACKWARD_COMPATIBLE } from './types'
22

33
export {
44
ClassifierType,
@@ -17,6 +17,8 @@ export { apiDiff } from './api'
1717
export type {
1818
CompareResult,
1919
CompareOptions,
20+
ApiCompatibilityScopeFunction,
21+
ApiCompatibilityKind,
2022
DiffType,
2123
ActionType,
2224
Diff,

src/openapi/openapi3.compare.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export const compareOpenApi = (version: OpenApiSpecVersion) => (before: unknown,
99
rules: openApi3Rules({
1010
mode: options.mode,
1111
version: version,
12+
operationSyntheticDiffs: options.openApiPathItemPerOperationDiffs
1213
}),
1314
},
1415
)

0 commit comments

Comments
 (0)