Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 62 additions & 6 deletions src/core/compare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { deepEqual } from 'fast-equals'
import {
AdapterContext,
AdapterResolver,
BwcState,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's be consistent with naming.
BwcState -> ApiCompatibilityScope
ApiCompatibilityKind: OK
backwardCompatibility -> apiCompatibilityScope
bwcScopeFunction -> apiCompatibilityScopeFunction
BwcScopeFunction -> ApiCompatibilityScopeFunction
parentBwc -> parentApiCompatibilityScope
computedBwc -> computedApiCompatibilityScope

CompareContext,
CompareResult,
CompareRule,
Expand All @@ -30,6 +31,7 @@ import {
JsonNode,
MergeState,
NodeContext,
ApiCompatibilityKind,
ValueTransformer,
} from '../types'
import { getObjectValue, isArray, isDiffAdd, isDiffRemove, isDiffReplace, isNumber, isObject, typeOf } from '../utils'
Expand Down Expand Up @@ -75,6 +77,7 @@ export const createContext = (data: ContextInput, options: InternalCompareOption
rules,
compareScope,
parentContext,
backwardCompatibility,
} = data
return {
parentContext: parentContext,
Expand All @@ -84,6 +87,7 @@ export const createContext = (data: ContextInput, options: InternalCompareOption
mergeKey,
rules,
options,
backwardCompatibility,
}
}

Expand All @@ -92,6 +96,7 @@ export const createChildContext = (
mergedKey: PropertyKey,
beforeChildKey: PropertyKey | undefined,
afterChildKey: PropertyKey | undefined,
backwardCompatibility: BwcState = ctx.backwardCompatibility,
): CompareContext => {
const { before, after, rules, options, scope } = ctx
let beforeContext: NodeContext
Expand Down Expand Up @@ -133,6 +138,7 @@ export const createChildContext = (
) ?? {},
options,
scope: scope,
backwardCompatibility,
}
}

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

export const getOrCreateChildDiffAdd = (diffUniquenessCache: EvaluationCacheService, childCtx: CompareContext) => {
const diff = diffUniquenessCache.cacheEvaluationResultByFootprint<[unknown, string, CompareScope, typeof DiffAction.add], DiffAdd>([childCtx.after.value, buildPathsIdentifier(childCtx.after.declarativePaths), childCtx.scope, DiffAction.add], () => {
const diff = diffUniquenessCache.cacheEvaluationResultByFootprint<[
unknown,
string,
CompareScope,
typeof DiffAction.add,
BwcState
], DiffAdd>([
childCtx.after.value,
buildPathsIdentifier(childCtx.after.declarativePaths),
childCtx.scope,
DiffAction.add,
childCtx.backwardCompatibility,
], () => {
return diffFactory.added(childCtx)
}, {} as DiffAdd, (result, guard) => {
Object.assign(guard, result)
Expand All @@ -167,7 +185,19 @@ export const getOrCreateChildDiffAdd = (diffUniquenessCache: EvaluationCacheServ
}

export const getOrCreateChildDiffRemove = (diffUniquenessCache: EvaluationCacheService, childCtx: CompareContext) => {
const diff = diffUniquenessCache.cacheEvaluationResultByFootprint<[unknown, string, CompareScope, typeof DiffAction.remove], DiffRemove>([childCtx.before.value, buildPathsIdentifier(childCtx.before.declarativePaths), childCtx.scope, DiffAction.remove], () => {
const diff = diffUniquenessCache.cacheEvaluationResultByFootprint<[
unknown,
string,
CompareScope,
typeof DiffAction.remove,
BwcState
], DiffRemove>([
childCtx.before.value,
buildPathsIdentifier(childCtx.before.declarativePaths),
childCtx.scope,
DiffAction.remove,
childCtx.backwardCompatibility,
], () => {
return diffFactory.removed(childCtx)
}, {} as DiffRemove, (result, guard) => {
Object.assign(guard, result)
Expand Down Expand Up @@ -203,7 +233,7 @@ const adaptValues = (beforeJso: JsonNode, beforeKey: PropertyKey, afterJso: Json
}

const useMergeFactory = (onDiff: DiffCallback, options: InternalCompareOptions): SyncCrawlHook<MergeState, CompareRule> => {
const { metaKey } = options
const { metaKey, bwcScopeFunction } = options
const diffs: Set<Diff> = new Set()
const addDiff: (diff: Diff) => void = (diff) => {
const oldSize = diffs.size
Expand All @@ -229,6 +259,7 @@ const useMergeFactory = (onDiff: DiffCallback, options: InternalCompareOptions):
diffUniquenessCache,
createdMergedJso,
compareScope,
backwardCompatibility: parentBwc,
} = state

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

const computedBwc = bwcScopeFunction?.(crawlContext.path, beforeValueAdapted, afterValueAdapted) ?? parentBwc

const ctx = createContext({
...state,
beforeValue: beforeValueAdapted,
Expand All @@ -261,12 +294,27 @@ const useMergeFactory = (onDiff: DiffCallback, options: InternalCompareOptions):
mergeKey,
rules,
compareScope: newCompareScope ?? compareScope,
backwardCompatibility: computedBwc,
}, options)

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

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]) => {
const reuseResult: ReusableMergeResult = mergedJsoCache.cacheEvaluationResultByFootprint<[
typeof ctx.before.value,
typeof ctx.after.value,
typeof beforeDeclarativePathsId,
typeof afterDeclarativePathsId,
CompareScope,
BwcState
], ReusableMergeResult>([
ctx.before.value,
ctx.after.value,
beforeDeclarativePathsId,
afterDeclarativePathsId,
ctx.scope,
computedBwc,
], ([beforeValue, afterValue]) => {
if (!ignoreKeyDifference && beforeKey !== afterKey) {
const diffEntry = createDiffEntry(ctx, diffFactory.renamed(ctx))
addDiff(diffEntry.diff)
Expand Down Expand Up @@ -310,13 +358,19 @@ const useMergeFactory = (onDiff: DiffCallback, options: InternalCompareOptions):
once = true

keyToRemove.forEach((keyToBefore) => {
const childCtx = createChildContext(ctx, keyToBefore, keyToBefore, undefined)
const removalBwc = computedBwc === ApiCompatibilityKind.NOT_BACKWARD_COMPATIBLE
? computedBwc
: bwcScopeFunction?.(crawlContext.path, beforeValue[keyToBefore])
const childCtx = createChildContext(ctx, keyToBefore, keyToBefore, undefined, removalBwc)
jsoDiffEntries.push(getOrCreateChildDiffRemove(diffUniquenessCache, childCtx))
})

keysToAdd.forEach((keyInAfter) => {
const additionBwc = computedBwc === ApiCompatibilityKind.NOT_BACKWARD_COMPATIBLE
? computedBwc
: bwcScopeFunction?.(crawlContext.path, undefined, afterJso[keyInAfter])
const keyInMerge = isArray(mergedJsoValue) ? mergedJsoValue.length : keyInAfter
const childCtx = createChildContext(ctx, keyInMerge, undefined, keyInAfter)
const childCtx = createChildContext(ctx, keyInMerge, undefined, keyInAfter, additionBwc)
jsoDiffEntries.push(getOrCreateChildDiffAdd(diffUniquenessCache, childCtx))
mergedJsoValue[keyInMerge] = afterValue[keyInAfter]
})
Expand Down Expand Up @@ -363,6 +417,7 @@ const useMergeFactory = (onDiff: DiffCallback, options: InternalCompareOptions):
afterJso: afterValueAdapted as JsonNode/*safe cause it only happens for object*/,
mergedJso: mergedValue,
compareScope: newCompareScope ?? compareScope,
backwardCompatibility: computedBwc,
}
return { value: reuseResult.nextValue, state: childState, exitHook: reuseResult.exitHook }
} else {
Expand Down Expand Up @@ -548,6 +603,7 @@ const compareInternal = (before: unknown, after: unknown, onDiff: DiffCallback,
diffUniquenessCache: options.diffUniquenessCache,
createdMergedJso: options.createdMergedJso,
compareScope: options.compareScope,
backwardCompatibility: ApiCompatibilityKind.BACKWARD_COMPATIBLE,
}
syncCrawl<MergeState, CompareRule>(before, [hook], { state: rootState, rules: options.rules })
return root.merged[JSO_ROOT]
Expand Down
24 changes: 21 additions & 3 deletions src/core/diff.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
import { JsonPath } from '@netcracker/qubership-apihub-json-crawl'

import { CompareContext, Diff, DiffAdd, DiffRemove, DiffReplace, DiffRename, DiffEntry, DiffFactory, DiffMetaRecord, NodeContext } from '../types'
import { allUnclassified, DiffAction, unclassified } from './constants'
import {
ApiCompatibilityKind,
CompareContext,
Diff,
DiffAdd,
DiffEntry,
DiffFactory,
DiffMetaRecord,
DiffRemove,
DiffRename,
DiffReplace,
DiffType,
NodeContext,
} from '../types'
import { allUnclassified, breaking, DiffAction, risky, unclassified } from './constants'
import { getKeyValue, isFunc } from '../utils'
import { calculateDefaultDiffDescription } from './description'

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

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

export const reclassifyBreakingToRisky = (type: DiffType, ctx: CompareContext): DiffType => {
return type === breaking && ctx.backwardCompatibility === ApiCompatibilityKind.NOT_BACKWARD_COMPATIBLE ? risky : type
}

export function createDiffEntry(ctx: CompareContext, diff: Diff): DiffEntry<Diff> {
return ({
propertyKey: ctx.mergeKey,
Expand Down
1 change: 0 additions & 1 deletion src/core/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ export const transformClassifyRule = ([add, remove, replace, reverseAdd, reverse
transformedRule(remove, DiffAction.remove),
transformedRule(replace, DiffAction.replace),
]

}

export const breakingIf = (v: boolean): DiffType => (v ? breaking : nonBreaking)
Expand Down
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export { apiDiff } from './api'
export type {
CompareResult,
CompareOptions,
BwcScopeFunction,
BwcState,
DiffType,
ActionType,
Diff,
Expand All @@ -26,6 +28,7 @@ export type {
DiffRename,
DiffMetaRecord,
} from './types'
export { ApiCompatibilityKind } from './types'

export {
isDiffAdd,
Expand Down
4 changes: 2 additions & 2 deletions src/openapi/openapi3.rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
allUnclassified,
breaking,
breakingIfAfterTrue,
deepEqualsUniqueItemsArrayMappingResolver,
diffDescription,
GREP_TEMPLATE_PARAM_ENCODING_NAME,
GREP_TEMPLATE_PARAM_EXAMPLE_NAME,
Expand All @@ -27,7 +28,6 @@ import {
TEMPLATE_PARAM_RESPONSE_PATH,
TEMPLATE_PARAM_SCOPE,
unclassified,
deepEqualsUniqueItemsArrayMappingResolver,
} from '../core'
import {
COMPARE_MODE_OPERATION,
Expand Down Expand Up @@ -62,7 +62,7 @@ import {
import { isResponseSchema } from './openapi3.utils'
import { apihubCaseInsensitiveKeyMappingResolver } from './mapping'
import { nonBreakingIf } from '../utils'
import { COMPARE_SCOPE_COMPONENTS, COMPARE_SCOPE_RESPONSE, COMPARE_SCOPE_REQUEST } from './openapi3.const'
import { COMPARE_SCOPE_COMPONENTS, COMPARE_SCOPE_REQUEST, COMPARE_SCOPE_RESPONSE } from './openapi3.const'
import { parameterParamsCalculator } from './openapi3.description.parameter'
import { requestParamsCalculator } from './openapi3.description.request'
import { responseParamsCalculator } from './openapi3.description.response'
Expand Down
11 changes: 11 additions & 0 deletions src/types/compare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,15 @@ export const COMPARE_MODE_OPERATION = 'operation'

export type CompareMode = typeof COMPARE_MODE_DEFAULT | typeof COMPARE_MODE_OPERATION

export enum ApiCompatibilityKind {
BACKWARD_COMPATIBLE = 'BACKWARD_COMPATIBLE',
NOT_BACKWARD_COMPATIBLE = 'NOT_BACKWARD_COMPATIBLE'
}

export type BwcState = keyof typeof ApiCompatibilityKind

export type BwcScopeFunction = (path?: JsonPath, beforeJso?: unknown, afterJso?: unknown) => BwcState | undefined

export interface CompareOptions extends Omit<NormalizeOptions, 'source'> {
mode?: CompareMode
normalizedResult?: boolean
Expand All @@ -92,6 +101,7 @@ export interface CompareOptions extends Omit<NormalizeOptions, 'source'> {
onCreateDiffError?: (message: string, diff: Diff, ctx: CompareContext) => void
beforeValueNormalizedProperty?: symbol
afterValueNormalizedProperty?: symbol
bwcScopeFunction?: BwcScopeFunction
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a documentation for this function to clarify it's intended use-case.

}

export type DiffCallback = (diff: Diff/*, ctx: CompareContext*/) => void
Expand Down Expand Up @@ -133,6 +143,7 @@ export interface MergeState<T extends PropertyKey = string> {
diffUniquenessCache: EvaluationCacheService,
createdMergedJso: Set<JsonNode>,
compareScope: CompareScope
backwardCompatibility: BwcState
}

export type JsonNode<Key extends PropertyKey = string> = Key extends (string | symbol) ? Record<string | symbol, unknown> : Record<number, unknown> | Array<unknown>
Expand Down
3 changes: 2 additions & 1 deletion src/types/rules.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { CrawlRules, JsonPath } from '@netcracker/qubership-apihub-json-crawl'

import type { CompareResult, Diff, DiffType } from './compare'
import { CompareScope, InternalCompareOptions } from './compare'
import { BwcState, CompareScope, InternalCompareOptions } from './compare'
import { DiffAction } from '../core'
import { OriginLeafs } from '@netcracker/qubership-apihub-api-unifier'

Expand Down Expand Up @@ -44,6 +44,7 @@ export interface CompareContext {
mergeKey: PropertyKey
rules: CompareRules
options: InternalCompareOptions
backwardCompatibility: BwcState
}

export interface AdapterContext<T> {
Expand Down
Loading