diff --git a/tools/spectral/ipa/__tests__/IPA119NoDefaultForCloudProviders.test.js b/tools/spectral/ipa/__tests__/IPA119NoDefaultForCloudProviders.test.js new file mode 100644 index 0000000000..408b0dd959 --- /dev/null +++ b/tools/spectral/ipa/__tests__/IPA119NoDefaultForCloudProviders.test.js @@ -0,0 +1,185 @@ +import testRule from './__helpers__/testRule'; +import { DiagnosticSeverity } from '@stoplight/types'; + +testRule('xgen-IPA-119-no-default-for-cloud-providers', [ + { + name: 'valid when no default in cloud provider field', + document: { + components: { + schemas: { + Schema: { + properties: { + cloudProvider: { + type: 'string', + enum: ['AWS', 'GCP', 'AZURE'], + }, + }, + }, + }, + }, + }, + errors: [], + }, + { + name: 'invalid when cloud provider field has default value', + document: { + components: { + schemas: { + Schema: { + properties: { + cloudProvider: { + type: 'string', + enum: ['AWS', 'GCP', 'AZURE'], + default: 'AWS', + }, + }, + }, + }, + }, + }, + errors: [ + { + code: 'xgen-IPA-119-no-default-for-cloud-providers', + message: 'When using a provider field or param, API producers should not define a default value.', + path: ['components', 'schemas', 'Schema', 'properties', 'cloudProvider'], + severity: DiagnosticSeverity.Warning, + }, + ], + }, + { + name: 'invalid when cloud provider field has default value', + document: { + components: { + schemas: { + Schema: { + properties: { + provider: { + type: 'string', + enum: ['AWS', 'GCP', 'AZURE'], + default: 'AWS', + }, + }, + }, + }, + }, + }, + errors: [ + { + code: 'xgen-IPA-119-no-default-for-cloud-providers', + message: 'When using a provider field or param, API producers should not define a default value.', + path: ['components', 'schemas', 'Schema', 'properties', 'provider'], + severity: DiagnosticSeverity.Warning, + }, + ], + }, + { + name: 'valid when non-provider field has default value', + document: { + components: { + schemas: { + Schema: { + properties: { + region: { + type: 'string', + default: 'us-east-1', + }, + }, + }, + }, + }, + }, + errors: [], + }, + { + name: 'field with provider in name has exception', + document: { + components: { + schemas: { + Schema: { + properties: { + cloudProvider: { + type: 'string', + enum: ['AWS', 'GCP', 'AZURE'], + default: 'AWS', + 'x-xgen-IPA-exception': { + 'xgen-IPA-119-no-default-for-cloud-providers': 'Reason', + }, + }, + }, + }, + }, + }, + }, + errors: [], + }, + { + name: 'parameters with provider in name', + document: { + paths: { + '/resources': { + get: { + parameters: [ + { + name: 'cloudProvider', + in: 'query', + schema: { + type: 'string', + default: 'AWS', + }, + }, + ], + }, + }, + }, + }, + errors: [ + { + code: 'xgen-IPA-119-no-default-for-cloud-providers', + message: 'When using a provider field or param, API producers should not define a default value.', + path: ['paths', '/resources', 'get', 'parameters', '0'], + severity: DiagnosticSeverity.Warning, + }, + ], + }, + { + name: 'parameters with provider in name - exceptions', + document: { + paths: { + '/resources': { + get: { + parameters: [ + { + name: 'cloudProvider', + in: 'query', + schema: { + type: 'string', + default: 'AWS', + }, + 'x-xgen-IPA-exception': { + 'xgen-IPA-119-no-default-for-cloud-providers': 'Reason', + }, + }, + ], + }, + }, + }, + components: { + schemas: { + Schema: { + properties: { + provider: { + type: 'string', + enum: ['AWS', 'GCP', 'AZURE'], + default: 'AWS', + 'x-xgen-IPA-exception': { + 'xgen-IPA-119-no-default-for-cloud-providers': 'Reason', + }, + }, + }, + }, + }, + }, + }, + errors: [], + }, +]); diff --git a/tools/spectral/ipa/ipa-spectral.yaml b/tools/spectral/ipa/ipa-spectral.yaml index c496d1dd0c..10d0ce7384 100644 --- a/tools/spectral/ipa/ipa-spectral.yaml +++ b/tools/spectral/ipa/ipa-spectral.yaml @@ -13,6 +13,7 @@ extends: - ./rulesets/IPA-114.yaml - ./rulesets/IPA-117.yaml - ./rulesets/IPA-118.yaml + - ./rulesets/IPA-119.yaml - ./rulesets/IPA-123.yaml - ./rulesets/IPA-125.yaml diff --git a/tools/spectral/ipa/rulesets/IPA-119.yaml b/tools/spectral/ipa/rulesets/IPA-119.yaml new file mode 100644 index 0000000000..d88132f731 --- /dev/null +++ b/tools/spectral/ipa/rulesets/IPA-119.yaml @@ -0,0 +1,28 @@ +# IPA-119: Multi-Cloud Support by Default +# http://go/ipa/119 + +functions: + - IPA119NoDefaultForCloudProviders +rules: + xgen-IPA-119-no-default-for-cloud-providers: + description: | + When using a provider field or parameter, API producers should not define a default value. + This rule checks fields and parameters named "cloudProvider" and ensures they do not have a default value. + It also checks enum fields that might contain cloud provider values. + All cloudProviderEnumValues should be listed in the enum array. + severity: warn + message: '{{error}} https://mdb.link/mongodb-atlas-openapi-validation#xgen-IPA-119-no-default-for-cloud-providers' + given: + # Target properties with "cloudProvider" in their name + - '$.paths[*][get,put,post,delete,options,head,patch,trace].parameters' + - '$.paths[*][get,put,post,delete,options,head,patch,trace]..content..properties' + - '$.components.schemas..properties' + then: + field: '@key' + function: IPA119NoDefaultForCloudProviders + functionOptions: + propertyNameToLookFor: 'cloudProvider' + cloudProviderEnumValues: + - 'AWS' + - 'GCP' + - 'AZURE' diff --git a/tools/spectral/ipa/rulesets/README.md b/tools/spectral/ipa/rulesets/README.md index 8c8a49fb7b..d1754972d2 100644 --- a/tools/spectral/ipa/rulesets/README.md +++ b/tools/spectral/ipa/rulesets/README.md @@ -788,6 +788,20 @@ This rule checks all nested schemas, but only parent schemas can be marked for e +### IPA-119 + +Rules are based on [http://go/ipa/IPA-119](http://go/ipa/IPA-119). + +#### xgen-IPA-119-no-default-for-cloud-providers + + ![warn](https://img.shields.io/badge/warning-yellow) +When using a provider field or parameter, API producers should not define a default value. +This rule checks fields and parameters named "cloudProvider" and ensures they do not have a default value. +It also checks enum fields that might contain cloud provider values. +All cloudProviderEnumValues should be listed in the enum array. + + + ### IPA-123 Rules are based on [http://go/ipa/IPA-123](http://go/ipa/IPA-123). diff --git a/tools/spectral/ipa/rulesets/functions/IPA119NoDefaultForCloudProviders.js b/tools/spectral/ipa/rulesets/functions/IPA119NoDefaultForCloudProviders.js new file mode 100644 index 0000000000..032e7c4590 --- /dev/null +++ b/tools/spectral/ipa/rulesets/functions/IPA119NoDefaultForCloudProviders.js @@ -0,0 +1,104 @@ +import { hasException } from './utils/exceptions.js'; +import { + collectAdoption, + collectAndReturnViolation, + collectException, + handleInternalError, +} from './utils/collectionUtils.js'; +import { resolveObject } from './utils/componentUtils.js'; + +const RULE_NAME = 'xgen-IPA-119-no-default-for-cloud-providers'; +const ERROR_MESSAGE = 'When using a provider field or param, API producers should not define a default value.'; +export default (input, { propertyNameToLookFor, cloudProviderEnumValues }, { path, documentInventory }) => { + const oas = documentInventory.resolved; + const propertyObject = resolveObject(oas, path); + const fieldType = path[path.length - 2]; + + if (fieldType === 'parameters' && !propertyObject.schema) { + return; + } + + if (hasException(propertyObject, RULE_NAME)) { + collectException(propertyObject, RULE_NAME, path); + return; + } + + const errors = checkViolationsAndReturnErrors( + input, + propertyObject, + path, + propertyNameToLookFor, + fieldType, + cloudProviderEnumValues + ); + if (errors.length !== 0) { + return collectAndReturnViolation(path, RULE_NAME, errors); + } + collectAdoption(path, RULE_NAME); +}; + +function checkViolationsAndReturnErrors( + propertyName, + propertyObject, + path, + propertyNameToLookFor, + fieldType, + cloudProviderEnumValues +) { + try { + if (fieldType === 'properties') { + if (propertyName === propertyNameToLookFor && propertyObject.default !== undefined) { + return [ + { + path, + message: ERROR_MESSAGE, + }, + ]; + } + + if (Array.isArray(propertyObject.enum) && propertyObject.enum.length > 0) { + const enumValues = propertyObject.enum; + const hasCloudProviderEnumValue = cloudProviderEnumValues.every((cloudProviderValue) => + enumValues.includes(cloudProviderValue) + ); + if (hasCloudProviderEnumValue && propertyObject.default !== undefined) { + return [ + { + path, + message: ERROR_MESSAGE, + }, + ]; + } + } + } else if (fieldType === 'parameters') { + if (propertyObject.name === propertyNameToLookFor && propertyObject.schema.default !== undefined) { + return [ + { + path, + message: ERROR_MESSAGE, + }, + ]; + } + + if (Array.isArray(propertyObject.schema.enum) && propertyObject.schema.enum.length > 0) { + const enumValues = propertyObject.schema.enum; + const hasCloudProviderEnumValue = cloudProviderEnumValues.every((cloudProviderValue) => + enumValues.includes(cloudProviderValue) + ); + + if (hasCloudProviderEnumValue && propertyObject.schema.default !== undefined) { + return [ + { + path, + message: ERROR_MESSAGE, + }, + ]; + } + } + } + + return []; + } catch (e) { + handleInternalError(RULE_NAME, path, e); + } +}