Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
d6376ab
chore: set feature version for dependecy
vOrigins Feb 27, 2026
366b68e
feat!: reclassify `add-required-property-with-default` as breaking fo…
vOrigins Feb 20, 2026
fc33893
feat!: reclassify `remove-property-compliance` as non-breaking for `s…
vOrigins Feb 24, 2026
1ff34d1
refactor: rename `remove-required-property` to `remove-required-statu…
vOrigins Feb 24, 2026
402a23c
feat: add `remove-required-property-compliance` case for schemas
vOrigins Feb 24, 2026
2e65733
feat!: reclassify `update-format-for-number-property` as risky for `r…
vOrigins Feb 25, 2026
b995526
feat!: reclassify `remove-format-for-number-property` as risky for `r…
vOrigins Feb 25, 2026
ce5a21b
feat!: reclassify `decrease-minimum-for-number-property` as risky for…
vOrigins Feb 26, 2026
ac6cfe3
feat: add `decrease-minimum-for-required-number-property` case for sc…
vOrigins Feb 26, 2026
d219665
feat!: reclassify `remove-minimum-for-number-property` as risky for `…
vOrigins Feb 26, 2026
847ff3d
feat: add `remove-minimum-for-required-number-property` case for schemas
vOrigins Feb 26, 2026
f684222
feat!: reclassify `mark-minimum-value-as-inclusive-for-number-propert…
vOrigins Feb 26, 2026
418f064
feat!: reclassify `update-multiple-of-for-number-property` as risky f…
vOrigins Feb 27, 2026
01d9988
feat!: reclassify `increase-max-properties-for-object-property` as ri…
vOrigins Feb 27, 2026
bc933ac
feat!: reclassify `remove-max-properties-for-object-property` as risk…
vOrigins Feb 27, 2026
d9dc50f
feat!: reclassify `add-non-boolean-additional-properties` as risky fo…
vOrigins Feb 27, 2026
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"fast-equals": "6.0.0"
},
"devDependencies": {
"@netcracker/qubership-apihub-compatibility-suites": "dev",
"@netcracker/qubership-apihub-compatibility-suites": "feature-rules-reclassification-part-1",
"@netcracker/qubership-apihub-graphapi": "1.0.9",
"@netcracker/qubership-apihub-npm-gitflow": "3.1.0",
"@types/jest": "30.0.0",
Expand Down
39 changes: 35 additions & 4 deletions src/jsonSchema/jsonSchema.classify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
nonBreaking,
PARENT_JUMP,
risky,
riskyIf,
strictResolveValueFromContext,
unclassified,
} from '../core'
Expand All @@ -26,13 +27,16 @@ export const typeClassifier: ClassifyRule = [
({ before, after }) => nonBreakingIf(isTypeAssignable(before.value, after.value, false)),
breaking,//not tested
breaking,//not tested
({ before, after }) => nonBreakingIf(isTypeAssignable(before.value, after.value, true)),
({ before, after }) => isTypeAssignable(before.value, after.value, true) ? risky : breaking,
]

export const maxClassifier: ClassifyRule = [
breaking,
nonBreaking,
({ before, after }) => breakingIf(!isNumber(before.value) || !isNumber(after.value) || before.value > after.value),
nonBreaking,
risky,
({ before, after }) => riskyIf(isNumber(before.value) && isNumber(after.value) && before.value < after.value),
]

export const minClassifier: ClassifyRule = [
Expand All @@ -48,6 +52,29 @@ export const minimumClassifier: ClassifyRule = [
},
nonBreaking,
({ before, after }) => breakingIf(!isNumber(before.value) || !isNumber(after.value) || before.value < after.value),
({ before, after }) => {
const beforeExclusiveMinimum = strictResolveValueFromContext(before, PARENT_JUMP, 'exclusiveMinimum')
return nonBreakingIf(!isNumber(beforeExclusiveMinimum) || !isNumber(after.value) || beforeExclusiveMinimum < after.value)
},
({ before }) => {
const propertyName = before.parentContext?.key
const requiredArray = getArrayValue(strictResolveValueFromContext(before, PARENT_JUMP, PARENT_JUMP, PARENT_JUMP, 'required'))
if (isString(propertyName) && requiredArray?.includes(propertyName)) {
return breaking
}
return risky
},
({ before, after }) => {
if (!isNumber(before.value) || !isNumber(after.value) || before.value < after.value) {
return nonBreaking
}
const propertyName = before.parentContext?.key
const requiredArray = getArrayValue(strictResolveValueFromContext(before, PARENT_JUMP, PARENT_JUMP, PARENT_JUMP, 'required'))
if (isString(propertyName) && requiredArray?.includes(propertyName)) {
return breaking
}
return risky
},
]

export const maximumClassifier: ClassifyRule = [
Expand All @@ -63,6 +90,9 @@ export const exclusiveClassifier: ClassifyRule = [
({ after }) => (after.value === true ? breaking : unclassified),
({ before }) => (before.value === true ? nonBreaking : unclassified),
breakingIfAfterTrue,
({ after }) => (after.value === true ? nonBreaking : unclassified),
({ before }) => (before.value === true ? risky : unclassified),
({ after }) => riskyIf(!after.value),
]

//todo think about replace multipleOf in inverse case
Expand All @@ -72,11 +102,12 @@ export const multipleOfClassifier: ClassifyRule = [
({ before, after }) => breakingIfNotMultiple(before.value, after.value),
nonBreaking,
breaking,
breaking,
risky,
]

export const requiredItemClassifyRule: ClassifyRule = [
({ after }) => (!isString(after.value) || isExist(strictResolveValueFromContext(after, PARENT_JUMP, PARENT_JUMP, 'properties', after.value, 'default')) ? nonBreaking : breaking),
// classification is the same, but we are keeping the code structure to be able to change it if needed
({ after }) => (!isString(after.value) || isExist(strictResolveValueFromContext(after, PARENT_JUMP, PARENT_JUMP, 'properties', after.value, 'default')) ? breaking : breaking),
nonBreaking,
({ after }) => (!isString(after.value) || isExist(strictResolveValueFromContext(after, PARENT_JUMP, PARENT_JUMP, 'properties', after.value, 'default')) ? nonBreaking : breaking),
nonBreaking,
Expand All @@ -90,7 +121,7 @@ export const propertyClassifyRule: ClassifyRule = [
!isExist(getKeyValue(after.value, 'default')) &&
getArrayValue((strictResolveValueFromContext(after, PARENT_JUMP, PARENT_JUMP, 'required')))?.includes(after.key) ? breaking : nonBreaking
),
breaking,
nonBreaking,
unclassified,
nonBreaking,
({ before }) => (getArrayValue(strictResolveValueFromContext(before, PARENT_JUMP, PARENT_JUMP, 'required'))?.includes(before.key) ? breaking : nonBreaking),
Expand Down
2 changes: 1 addition & 1 deletion src/jsonSchema/jsonSchema.rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export const jsonSchemaRules = ({
},
},

'/format': simpleRule([breaking, nonBreaking, breaking, nonBreaking, breaking, breaking], resolveSchemaDescriptionTemplates('format')),
'/format': simpleRule([breaking, nonBreaking, breaking, nonBreaking, risky, risky], resolveSchemaDescriptionTemplates('format')),
'/default': simpleRule([nonBreaking, breaking, breaking], resolveSchemaDescriptionTemplates('default value')),

'/enum': {
Expand Down
75 changes: 57 additions & 18 deletions test/compatibility-suites/schemas/schema-test-runner-general.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ export function runGeneralSchemaTests(
action: DiffAction.replace,
beforeDeclarationPaths: [[...commonPath, 'format']],
afterDeclarationPaths: [[...commonPath, 'format']],
type: breaking,
type: expectedType(breaking, risky),
}),
]))
})
Expand All @@ -176,7 +176,7 @@ export function runGeneralSchemaTests(
expect.objectContaining({
action: DiffAction.remove,
beforeDeclarationPaths: [[...commonPath, 'format']],
type: expectedType(nonBreaking, breaking),
type: expectedType(nonBreaking, risky),
}),
]))
})
Expand Down Expand Up @@ -259,7 +259,7 @@ export function runGeneralSchemaTests(
action: DiffAction.replace,
beforeDeclarationPaths: [[...commonPath, 'maxLength']],
afterDeclarationPaths: [[...commonPath, 'maxLength']],
type: expectedType(nonBreaking, breaking),
type: expectedType(nonBreaking, risky),
}),
]))
})
Expand All @@ -282,7 +282,7 @@ export function runGeneralSchemaTests(
expect.objectContaining({
action: DiffAction.remove,
beforeDeclarationPaths: [[...commonPath, 'maxLength']],
type: expectedType(nonBreaking, breaking),
type: expectedType(nonBreaking, risky),
}),
]))
})
Expand Down Expand Up @@ -339,7 +339,7 @@ export function runGeneralSchemaTests(
action: DiffAction.replace,
beforeDeclarationPaths: [[...commonPath, 'format']],
afterDeclarationPaths: [[...commonPath, 'format']],
type: breaking,
type: expectedType(breaking, risky),
}),
]))
})
Expand All @@ -350,7 +350,7 @@ export function runGeneralSchemaTests(
expect.objectContaining({
action: DiffAction.remove,
beforeDeclarationPaths: [[...commonPath, 'format']],
type: expectedType(nonBreaking, breaking),
type: expectedType(nonBreaking, risky),
}),
]))
})
Expand Down Expand Up @@ -385,6 +385,18 @@ export function runGeneralSchemaTests(
action: DiffAction.replace,
beforeDeclarationPaths: [[...commonPath, 'minimum']],
afterDeclarationPaths: [[...commonPath, 'minimum']],
type: expectedType(nonBreaking, risky),
}),
]))
})

test('decrease-minimum-for-required-number-property', async () => {
const result = await compareFiles(suiteId, currentTestId(), suiteType)
expect(result).toEqual(diffsMatcher([
expect.objectContaining({
action: DiffAction.replace,
beforeDeclarationPaths: [[...commonPath, 'properties', 'prop1', 'minimum']],
afterDeclarationPaths: [[...commonPath, 'properties', 'prop1', 'minimum']],
type: expectedType(nonBreaking, breaking),
}),
]))
Expand All @@ -396,6 +408,17 @@ export function runGeneralSchemaTests(
expect.objectContaining({
action: DiffAction.remove,
beforeDeclarationPaths: [[...commonPath, 'minimum']],
type: expectedType(nonBreaking, risky),
}),
]))
})

test('remove-minimum-for-required-number-property', async () => {
const result = await compareFiles(suiteId, currentTestId(), suiteType)
expect(result).toEqual(diffsMatcher([
expect.objectContaining({
action: DiffAction.remove,
beforeDeclarationPaths: [[...commonPath, 'properties', 'prop1', 'minimum']],
type: expectedType(nonBreaking, breaking),
}),
]))
Expand Down Expand Up @@ -465,7 +488,7 @@ export function runGeneralSchemaTests(
action: DiffAction.replace,
beforeDeclarationPaths: [[...commonPath, 'multipleOf']],
afterDeclarationPaths: [[...commonPath, 'multipleOf']],
type: breaking,
type: expectedType(breaking, risky),
}),
]))
})
Expand Down Expand Up @@ -547,7 +570,7 @@ export function runGeneralSchemaTests(
action: DiffAction.replace,
beforeDeclarationPaths: [[...commonPath, 'maxItems']],
afterDeclarationPaths: [[...commonPath, 'maxItems']],
type: expectedType(nonBreaking, breaking),
type: expectedType(nonBreaking, risky),
}),
]))
})
Expand All @@ -570,7 +593,7 @@ export function runGeneralSchemaTests(
expect.objectContaining({
action: DiffAction.remove,
beforeDeclarationPaths: [[...commonPath, 'maxItems']],
type: expectedType(nonBreaking, breaking),
type: expectedType(nonBreaking, risky),
}),
]))
})
Expand Down Expand Up @@ -628,7 +651,7 @@ export function runGeneralSchemaTests(
expect.objectContaining({
action: DiffAction.remove,
beforeDeclarationPaths: [[...commonPath, 'properties', 'prop2']],
type: expectedType(breaking, nonBreaking),
type: nonBreaking,
}),
]))
})
Expand All @@ -655,17 +678,17 @@ export function runGeneralSchemaTests(
expect.objectContaining({
action: DiffAction.add,
afterDeclarationPaths: [[...commonPath, 'required', 0]],
type: nonBreaking,
type: expectedType(breaking, nonBreaking),
}),
expect.objectContaining({
action: DiffAction.add,
afterDeclarationPaths: [[...commonPath, 'required', 1]],
type: nonBreaking,
type: expectedType(breaking, nonBreaking),
}),
]))
})

test('remove-required-property', async () => {
test('remove-required-status-from-property', async () => {
const result = await compareFiles(suiteId, currentTestId(), suiteType)
expect(result).toEqual(diffsMatcher([
expect.objectContaining({
Expand All @@ -676,6 +699,22 @@ export function runGeneralSchemaTests(
]))
})

test('remove-required-property-compliance', async () => {
const result = await compareFiles(suiteId, currentTestId(), suiteType)
expect(result).toEqual(diffsMatcher([
expect.objectContaining({
action: DiffAction.remove,
beforeDeclarationPaths: [[...commonPath, 'required', 0]],
type: expectedType(nonBreaking, breaking),
}),
expect.objectContaining({
action: DiffAction.remove,
beforeDeclarationPaths: [[...commonPath, 'properties', 'prop2']],
type: expectedType(nonBreaking, breaking),
}),
]))
})

test('update-required-property', async () => {
const result = await compareFiles(suiteId, currentTestId(), suiteType)
expect(result).toEqual(diffsMatcher([
Expand Down Expand Up @@ -830,7 +869,7 @@ export function runGeneralSchemaTests(
action: DiffAction.replace,
beforeDeclarationPaths: [[...commonPath, 'maxProperties']],
afterDeclarationPaths: [[...commonPath, 'maxProperties']],
type: expectedType(nonBreaking, breaking),
type: expectedType(nonBreaking, risky),
}),
]))
})
Expand All @@ -853,7 +892,7 @@ export function runGeneralSchemaTests(
expect.objectContaining({
action: DiffAction.remove,
beforeDeclarationPaths: [[...commonPath, 'maxProperties']],
type: expectedType(nonBreaking, breaking),
type: expectedType(nonBreaking, risky),
}),
]))
})
Expand All @@ -876,7 +915,7 @@ export function runGeneralSchemaTests(
afterValue: 'string',
beforeDeclarationPaths: TEST_DEFAULTS_DECLARATION_PATHS,
afterDeclarationPaths: [[...commonPath, 'additionalProperties', 'type']],
type: expectedType(breaking, nonBreaking),
type: expectedType(breaking, risky),
}),
]),
)
Expand Down Expand Up @@ -1026,7 +1065,7 @@ export function runGeneralSchemaTests(
expect.objectContaining({
action: DiffAction.remove,
beforeDeclarationPaths: [[...commonPath, 'allOf', 2, 'properties', 'prop3']],
type: expectedType(breaking, nonBreaking),
type: nonBreaking,
}),
]))
})
Expand All @@ -1037,7 +1076,7 @@ export function runGeneralSchemaTests(
expect.objectContaining({
action: DiffAction.remove,
beforeDeclarationPaths: [[...commonPath, 'allOf', 1, 'properties', 'prop2']],
type: expectedType(breaking, nonBreaking),
type: nonBreaking,
}),
]))
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -333,13 +333,13 @@ export function runOpenApiOnlySchemaTests(
action: DiffAction.replace,
beforeDeclarationPaths: [[...commonPath, 'properties', 'option1', 'exclusiveMinimum']],
afterDeclarationPaths: TEST_DEFAULTS_DECLARATION_PATHS,
type: expectedType(nonBreaking, breaking),
type: expectedType(nonBreaking, risky),
}),
expect.objectContaining({
action: DiffAction.replace,
beforeDeclarationPaths: [[...commonPath, 'properties', 'option2', 'exclusiveMinimum']],
afterDeclarationPaths: [[...commonPath, 'properties', 'option2', 'exclusiveMinimum']],
type: expectedType(nonBreaking, breaking),
type: expectedType(nonBreaking, risky),
}),
]))
})
Expand Down Expand Up @@ -367,13 +367,13 @@ export function runOpenApiOnlySchemaTests(
action: DiffAction.replace,
beforeDeclarationPaths: [[...commonPath, 'properties', 'option1', 'exclusiveMaximum']],
afterDeclarationPaths: TEST_DEFAULTS_DECLARATION_PATHS,
type: expectedType(nonBreaking, breaking),
type: expectedType(nonBreaking, risky),
}),
expect.objectContaining({
action: DiffAction.replace,
beforeDeclarationPaths: [[...commonPath, 'properties', 'option2', 'exclusiveMaximum']],
afterDeclarationPaths: [[...commonPath, 'properties', 'option2', 'exclusiveMaximum']],
type: expectedType(nonBreaking, breaking),
type: expectedType(nonBreaking, risky),
}),
]))
})
Expand Down Expand Up @@ -455,7 +455,7 @@ export function runOpenApiOnlySchemaTests(
action: DiffAction.replace,
beforeDeclarationPaths: [[...commonPath, 'maxLength']],
afterDeclarationPaths: [[...COMPONENTS_SCHEMAS, 'Color', 'maxLength']],
type: expectedType(nonBreaking, breaking),
type: expectedType(nonBreaking, risky),
}),
]))
},
Expand Down
Loading