diff --git a/tools/spectral/ipa/__tests__/IPA112AvoidProjectFieldNames.test.js b/tools/spectral/ipa/__tests__/IPA112AvoidProjectFieldNames.test.js new file mode 100644 index 0000000000..3e6e8f4741 --- /dev/null +++ b/tools/spectral/ipa/__tests__/IPA112AvoidProjectFieldNames.test.js @@ -0,0 +1,234 @@ +import testRule from './__helpers__/testRule'; +import { DiagnosticSeverity } from '@stoplight/types'; + +testRule('xgen-IPA-112-avoid-project-field-names', [ + { + name: 'valid schema - no project field names', + document: { + components: { + schemas: { + SchemaName: { + properties: { + group: { type: 'string' }, + groupId: { type: 'string' }, + otherField: { type: 'number' }, + }, + }, + }, + }, + }, + errors: [], + }, + { + name: 'invalid schema - with project field name', + document: { + components: { + schemas: { + SchemaName: { + properties: { + project: { type: 'string' }, + }, + }, + }, + }, + }, + errors: [ + { + code: 'xgen-IPA-112-avoid-project-field-names', + message: 'Field name "project" should be avoided. Consider using "group" instead.', + path: ['components', 'schemas', 'SchemaName', 'properties', 'project'], + severity: DiagnosticSeverity.Warning, + }, + ], + }, + { + name: 'invalid schema - with projects field name', + document: { + components: { + schemas: { + SchemaName: { + properties: { + projects: { type: 'array' }, + }, + }, + }, + }, + }, + errors: [ + { + code: 'xgen-IPA-112-avoid-project-field-names', + message: 'Field name "projects" should be avoided. Consider using "group" instead.', + path: ['components', 'schemas', 'SchemaName', 'properties', 'projects'], + severity: DiagnosticSeverity.Warning, + }, + ], + }, + { + name: 'invalid schema - with projectId field name', + document: { + components: { + schemas: { + SchemaName: { + properties: { + projectId: { type: 'string' }, + }, + }, + }, + }, + }, + errors: [ + { + code: 'xgen-IPA-112-avoid-project-field-names', + message: 'Field name "projectId" should be avoided. Consider using "group" instead.', + path: ['components', 'schemas', 'SchemaName', 'properties', 'projectId'], + severity: DiagnosticSeverity.Warning, + }, + ], + }, + { + name: 'invalid schema - with different case variants', + document: { + components: { + schemas: { + SchemaName: { + properties: { + Project: { type: 'string' }, + PROJECTID: { type: 'string' }, + }, + }, + }, + }, + }, + errors: [ + { + code: 'xgen-IPA-112-avoid-project-field-names', + message: 'Field name "Project" should be avoided. Consider using "group" instead.', + path: ['components', 'schemas', 'SchemaName', 'properties', 'Project'], + severity: DiagnosticSeverity.Warning, + }, + { + code: 'xgen-IPA-112-avoid-project-field-names', + message: 'Field name "PROJECTID" should be avoided. Consider using "group" instead.', + path: ['components', 'schemas', 'SchemaName', 'properties', 'PROJECTID'], + severity: DiagnosticSeverity.Warning, + }, + ], + }, + { + name: 'invalid schema with exception - project field name with exception', + document: { + components: { + schemas: { + SchemaName: { + properties: { + project: { + type: 'string', + 'x-xgen-IPA-exception': { + 'xgen-IPA-112-avoid-project-field-names': 'reason', + }, + }, + }, + }, + }, + }, + }, + errors: [], + }, + { + name: 'field name containing project substring', + document: { + components: { + schemas: { + SchemaName: { + properties: { + myProjectDetails: { type: 'object' }, + }, + }, + }, + }, + }, + errors: [ + { + code: 'xgen-IPA-112-avoid-project-field-names', + message: 'Field name "myProjectDetails" should be avoided. Consider using "group" instead.', + path: ['components', 'schemas', 'SchemaName', 'properties', 'myProjectDetails'], + severity: DiagnosticSeverity.Warning, + }, + ], + }, + { + name: 'exception - field with project substring', + document: { + components: { + schemas: { + SchemaName: { + properties: { + myProjectDetails: { + type: 'object', + 'x-xgen-IPA-exception': { + 'xgen-IPA-112-avoid-project-field-names': 'Reason', + }, + }, + }, + }, + }, + }, + }, + errors: [], + }, + { + name: 'exception - multiple project fields', + document: { + components: { + schemas: { + SchemaName: { + properties: { + projectId: { + type: 'string', + 'x-xgen-IPA-exception': { + 'xgen-IPA-112-avoid-project-field-names': 'Reason', + }, + }, + projects: { + type: 'array', + 'x-xgen-IPA-exception': { + 'xgen-IPA-112-avoid-project-field-names': 'Reason', + }, + }, + }, + }, + }, + }, + }, + errors: [], + }, + { + name: 'mixed valid, invalid, and exception fields', + document: { + components: { + schemas: { + SchemaName: { + properties: { + project: { + type: 'string', + 'x-xgen-IPA-exception': { + 'xgen-IPA-112-avoid-project-field-names': 'Reason', + }, + }, + projectId: { type: 'string' }, + group: { type: 'string' }, + }, + }, + }, + }, + }, + errors: [ + { + code: 'xgen-IPA-112-avoid-project-field-names', + message: 'Field name "projectId" should be avoided. Consider using "group" instead.', + path: ['components', 'schemas', 'SchemaName', 'properties', 'projectId'], + severity: DiagnosticSeverity.Warning, + }, + ], + }, +]); diff --git a/tools/spectral/ipa/ipa-spectral.yaml b/tools/spectral/ipa/ipa-spectral.yaml index 582cf47cef..2b1a158f78 100644 --- a/tools/spectral/ipa/ipa-spectral.yaml +++ b/tools/spectral/ipa/ipa-spectral.yaml @@ -7,6 +7,7 @@ extends: - ./rulesets/IPA-107.yaml - ./rulesets/IPA-108.yaml - ./rulesets/IPA-109.yaml + - ./rulesets/IPA-112.yaml - ./rulesets/IPA-113.yaml - ./rulesets/IPA-123.yaml diff --git a/tools/spectral/ipa/rulesets/IPA-112.yaml b/tools/spectral/ipa/rulesets/IPA-112.yaml new file mode 100644 index 0000000000..6ebe32c58e --- /dev/null +++ b/tools/spectral/ipa/rulesets/IPA-112.yaml @@ -0,0 +1,26 @@ +# IPA-112: Field Names +# http://go/ipa/112 + +functions: + - IPA112AvoidProjectFieldNames + +rules: + xgen-IPA-112-avoid-project-field-names: + description: | + Schema field names should avoid using "project", "projects", or "projectId". + + ##### Implementation details + Rule checks for the following conditions: + - Searches through all schemas in the API definition + - Identifies property names that match "project" (case-insensitive) + - Reports any instances where these field names are used + - Suggests using "group", "groups", or "groupId" as alternatives + message: '{{error}} https://mdb.link/mongodb-atlas-openapi-validation#xgen-IPA-112-avoid-project-field-names' + severity: warn + given: '$.components.schemas..properties[*]~' + then: + function: 'IPA112AvoidProjectFieldNames' + functionOptions: + prohibitedFieldNames: + - name: 'project' + alternative: ['group'] diff --git a/tools/spectral/ipa/rulesets/README.md b/tools/spectral/ipa/rulesets/README.md index 91ae0f2e58..f8973ba55a 100644 --- a/tools/spectral/ipa/rulesets/README.md +++ b/tools/spectral/ipa/rulesets/README.md @@ -426,6 +426,24 @@ Rule checks for the following conditions: +### IPA-112 + +Rules are based on [http://go/ipa/IPA-112](http://go/ipa/IPA-112). + +#### xgen-IPA-112-avoid-project-field-names + + ![warn](https://img.shields.io/badge/warning-yellow) +Schema field names should avoid using "project", "projects", or "projectId". + +##### Implementation details +Rule checks for the following conditions: + - Searches through all schemas in the API definition + - Identifies property names that match "project" (case-insensitive) + - Reports any instances where these field names are used + - Suggests using "group", "groups", or "groupId" as alternatives + + + ### IPA-113 Rules are based on [http://go/ipa/IPA-113](http://go/ipa/IPA-113). diff --git a/tools/spectral/ipa/rulesets/functions/IPA112AvoidProjectFieldNames.js b/tools/spectral/ipa/rulesets/functions/IPA112AvoidProjectFieldNames.js new file mode 100644 index 0000000000..c68ded5b82 --- /dev/null +++ b/tools/spectral/ipa/rulesets/functions/IPA112AvoidProjectFieldNames.js @@ -0,0 +1,52 @@ +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-112-avoid-project-field-names'; + +export default (input, options, { path, documentInventory }) => { + const oas = documentInventory.resolved; + const property = resolveObject(oas, path); + + if (hasException(property, RULE_NAME)) { + collectException(property, RULE_NAME, path); + return; + } + + const errors = checkViolationsAndReturnErrors(input, options, path); + if (errors.length !== 0) { + return collectAndReturnViolation(path, RULE_NAME, errors); + } + collectAdoption(path, RULE_NAME); +}; + +function checkViolationsAndReturnErrors(input, options, path) { + try { + const prohibitedFieldNames = options?.prohibitedFieldNames || []; + const lowerPropertyName = input.toLowerCase(); + + // Check if the property name includes any of the prohibited terms + for (const prohibitedItem of prohibitedFieldNames) { + const prohibitedName = prohibitedItem.name || ''; + const alternative = prohibitedItem.alternative || ''; + + if (lowerPropertyName.includes(prohibitedName.toLowerCase())) { + return [ + { + path, + message: `Field name "${input}" should be avoided. Consider using ${alternative.map((alt) => `"${alt}"`)} instead.`, + }, + ]; + } + } + + return []; + } catch (e) { + handleInternalError(RULE_NAME, path, e); + } +}