diff --git a/tools/spectral/ipa/__tests__/IPA125DiscriminatorMustAccompanyOneOfAnyOfAllOf.test.js b/tools/spectral/ipa/__tests__/IPA125DiscriminatorMustAccompanyOneOfAnyOfAllOf.test.js new file mode 100644 index 0000000000..b4859bd16d --- /dev/null +++ b/tools/spectral/ipa/__tests__/IPA125DiscriminatorMustAccompanyOneOfAnyOfAllOf.test.js @@ -0,0 +1,128 @@ +import testRule from './__helpers__/testRule.js'; +import { DiagnosticSeverity } from '@stoplight/types'; + +testRule('xgen-IPA-125-discriminator-must-accompany-oneOf-anyOf-allOf', [ + { + name: 'valid schemas', + document: { + components: { + schemas: { + SchemaOneOf: { + discriminator: { + propertyName: 'type', + }, + oneOf: [{}], + }, + SchemaAnyOf: { + discriminator: { + propertyName: 'type', + }, + anyOf: [{}], + }, + SchemaAllOf: { + discriminator: { + propertyName: 'type', + }, + allOf: [{}], + }, + }, + }, + }, + errors: [], + }, + { + name: 'invalid schemas', + document: { + components: { + schemas: { + Schema: { + discriminator: { + propertyName: 'type', + }, + properties: { + type: { + type: 'string', + }, + }, + }, + NestedSchema: { + properties: { + name: { + type: 'object', + discriminator: { + propertyName: 'first', + }, + properties: { + first: { + type: 'string', + }, + }, + }, + address: { + type: 'string', + }, + }, + }, + }, + }, + }, + errors: [ + { + code: 'xgen-IPA-125-discriminator-must-accompany-oneOf-anyOf-allOf', + message: "Each discriminator property must be accompanied by a 'oneOf', 'anyOf' or 'allOf' property.", + path: ['components', 'schemas', 'Schema'], + severity: DiagnosticSeverity.Warning, + }, + { + code: 'xgen-IPA-125-discriminator-must-accompany-oneOf-anyOf-allOf', + message: "Each discriminator property must be accompanied by a 'oneOf', 'anyOf' or 'allOf' property.", + path: ['components', 'schemas', 'NestedSchema', 'properties', 'name'], + severity: DiagnosticSeverity.Warning, + }, + ], + }, + { + name: 'invalid schemas with exceptions', + document: { + components: { + schemas: { + Schema: { + discriminator: { + propertyName: 'type', + }, + properties: { + type: { + type: 'string', + }, + }, + 'x-xgen-IPA-exception': { + 'xgen-IPA-125-discriminator-must-accompany-oneOf-anyOf-allOf': 'reason', + }, + }, + NestedSchema: { + properties: { + name: { + type: 'object', + discriminator: { + propertyName: 'first', + }, + properties: { + first: { + type: 'string', + }, + }, + 'x-xgen-IPA-exception': { + 'xgen-IPA-125-discriminator-must-accompany-oneOf-anyOf-allOf': 'reason', + }, + }, + address: { + type: 'string', + }, + }, + }, + }, + }, + }, + errors: [], + }, +]); diff --git a/tools/spectral/ipa/rulesets/IPA-125.yaml b/tools/spectral/ipa/rulesets/IPA-125.yaml index 4bcd53a84c..a4d82eb851 100644 --- a/tools/spectral/ipa/rulesets/IPA-125.yaml +++ b/tools/spectral/ipa/rulesets/IPA-125.yaml @@ -5,6 +5,7 @@ functions: - IPA125OneOfMustHaveDiscriminator - IPA125OneOfNoBaseTypes - IPA125OneOfSchemaPropertySameType + - IPA125DiscriminatorMustAccompanyOneOfAnyOfAllOf rules: xgen-IPA-125-oneOf-must-have-discriminator: @@ -79,3 +80,15 @@ rules: given: '$.components.schemas..oneOf' then: function: 'IPA125OneOfSchemaPropertySameType' + + xgen-IPA-125-discriminator-must-accompany-oneOf-anyOf-allOf: + description: | + Each discriminator property must be accompanied by a `oneOf`, `anyOf` or `allOf` property + + ##### Implementation details + - Rule checks that a `discriminator` property has a `oneOf`, `anyOf` or `allOf` sibling + message: '{{error}} https://mdb.link/mongodb-atlas-openapi-validation#xgen-IPA-125-discriminator-must-accompany-oneOf-anyOf-allOf' + severity: warn + given: '$..discriminator' + then: + function: 'IPA125DiscriminatorMustAccompanyOneOfAnyOfAllOf' diff --git a/tools/spectral/ipa/rulesets/README.md b/tools/spectral/ipa/rulesets/README.md index 0f58a44dd1..67f8dc296c 100644 --- a/tools/spectral/ipa/rulesets/README.md +++ b/tools/spectral/ipa/rulesets/README.md @@ -999,6 +999,14 @@ Rule checks for the following conditions: - Applies only to object type schemas with `oneOf` - Ensures that if a property is defined in multiple `oneOf` schemas, it must have the same type in each schema (base type or object schema) +#### xgen-IPA-125-discriminator-must-accompany-oneOf-anyOf-allOf + + ![warn](https://img.shields.io/badge/warning-yellow) +Each discriminator property must be accompanied by a `oneOf`, `anyOf` or `allOf` property + +##### Implementation details +- Rule checks that a `discriminator` property has a `oneOf`, `anyOf` or `allOf` sibling + ### IPA-126 diff --git a/tools/spectral/ipa/rulesets/functions/IPA125DiscriminatorMustAccompanyOneOfAnyOfAllOf.js b/tools/spectral/ipa/rulesets/functions/IPA125DiscriminatorMustAccompanyOneOfAnyOfAllOf.js new file mode 100644 index 0000000000..7d456a7b63 --- /dev/null +++ b/tools/spectral/ipa/rulesets/functions/IPA125DiscriminatorMustAccompanyOneOfAnyOfAllOf.js @@ -0,0 +1,24 @@ +import { evaluateAndCollectAdoptionStatus } from './utils/collectionUtils.js'; +import { resolveObject } from './utils/componentUtils.js'; + +const ERROR_MESSAGE = "Each discriminator property must be accompanied by a 'oneOf', 'anyOf' or 'allOf' property."; + +export default (input, _, { path, documentInventory, rule }) => { + const siblingPath = path.slice(0, path.length - 1); + const siblings = resolveObject(documentInventory.resolved, siblingPath); + + const errors = checkViolationsAndReturnErrors(input, siblingPath, Object.keys(siblings)); + return evaluateAndCollectAdoptionStatus(errors, rule.name, siblings, siblingPath); +}; + +function checkViolationsAndReturnErrors(input, path, siblingKeys) { + if (!siblingKeys.includes('oneOf') && !siblingKeys.includes('anyOf') && !siblingKeys.includes('allOf')) { + return [ + { + path, + message: ERROR_MESSAGE, + }, + ]; + } + return []; +}