diff --git a/tools/spectral/ipa/__tests__/IPA112AvoidProjectFieldNames.test.js b/tools/spectral/ipa/__tests__/IPA112AvoidProjectFieldNames.test.js index 21642bfc1e..9fa0607339 100644 --- a/tools/spectral/ipa/__tests__/IPA112AvoidProjectFieldNames.test.js +++ b/tools/spectral/ipa/__tests__/IPA112AvoidProjectFieldNames.test.js @@ -18,6 +18,41 @@ testRule('xgen-IPA-112-avoid-project-field-names', [ }, }, }, + paths: { + '/users': { + post: { + requestBody: { + content: { + 'application/vnd.atlas.2024-01-01+json': { + schema: { + type: 'object', + properties: { + group: { type: 'string' }, + groupId: { type: 'string' }, + gcpProjectId: { type: 'string' }, + }, + }, + }, + }, + }, + responses: { + 201: { + content: { + 'application/vnd.atlas.2024-01-01+json': { + schema: { + type: 'object', + properties: { + group: { type: 'string' }, + gcpProjectId: { type: 'string' }, + }, + }, + }, + }, + }, + }, + }, + }, + }, }, errors: [], }, @@ -29,6 +64,41 @@ testRule('xgen-IPA-112-avoid-project-field-names', [ SchemaName: { properties: { project: { type: 'string' }, + projects: { type: 'array' }, + projectId: { type: 'string' }, + myProjectDetails: { type: 'object' }, + }, + }, + }, + }, + paths: { + '/users': { + post: { + requestBody: { + content: { + 'application/vnd.atlas.2024-01-01+json': { + schema: { + type: 'object', + properties: { + projectId: { type: 'string' }, + }, + }, + }, + }, + }, + responses: { + 201: { + content: { + 'application/vnd.atlas.2024-01-01+json': { + schema: { + type: 'object', + properties: { + projectId: { type: 'string' }, + }, + }, + }, + }, + }, }, }, }, @@ -41,105 +111,70 @@ testRule('xgen-IPA-112-avoid-project-field-names', [ 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 "groups" 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 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, }, + { + code: 'xgen-IPA-112-avoid-project-field-names', + message: 'Field name "projectId" should be avoided. Consider using "group" instead.', + path: [ + 'paths', + '/users', + 'post', + 'requestBody', + 'content', + 'application/vnd.atlas.2024-01-01+json', + 'schema', + 'properties', + 'projectId', + ], + severity: DiagnosticSeverity.Warning, + }, + { + code: 'xgen-IPA-112-avoid-project-field-names', + message: 'Field name "projectId" should be avoided. Consider using "group" instead.', + path: [ + 'paths', + '/users', + 'post', + 'responses', + '201', + 'content', + 'application/vnd.atlas.2024-01-01+json', + 'schema', + 'properties', + 'projectId', + ], + severity: DiagnosticSeverity.Warning, + }, ], }, { - name: 'exception - field with project substring', + name: 'invalid schema with exception - project field name with exception', document: { components: { schemas: { SchemaName: { properties: { - myProjectDetails: { - type: 'object', + project: { + type: 'string', 'x-xgen-IPA-exception': { - 'xgen-IPA-112-avoid-project-field-names': 'Reason', + 'xgen-IPA-112-avoid-project-field-names': 'reason', }, }, }, @@ -168,40 +203,17 @@ testRule('xgen-IPA-112-avoid-project-field-names', [ 'xgen-IPA-112-avoid-project-field-names': 'Reason', }, }, - }, - }, - }, - }, - }, - errors: [], - }, - { - name: 'mixed valid, invalid, and exception fields', - document: { - components: { - schemas: { - SchemaName: { - properties: { - project: { - type: 'string', + myProjectDetails: { + type: 'object', '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, - }, - ], + errors: [], }, ]); diff --git a/tools/spectral/ipa/__tests__/IPA112FieldNamesAreCamelCase.test.js b/tools/spectral/ipa/__tests__/IPA112FieldNamesAreCamelCase.test.js new file mode 100644 index 0000000000..bb3c134a34 --- /dev/null +++ b/tools/spectral/ipa/__tests__/IPA112FieldNamesAreCamelCase.test.js @@ -0,0 +1,464 @@ +import testRule from './__helpers__/testRule'; +import { DiagnosticSeverity } from '@stoplight/types'; + +testRule('xgen-IPA-112-field-names-are-camel-case', [ + { + name: 'valid examples', + document: { + components: { + schemas: { + SchemaName: { + properties: { + userId: { type: 'string' }, + firstName: { type: 'string' }, + emailAddress: { type: 'string' }, + phoneNumber: { type: 'string' }, + }, + }, + }, + }, + paths: { + '/users': { + post: { + requestBody: { + content: { + 'application/vnd.atlas.2024-01-01+json': { + schema: { + type: 'object', + properties: { + firstName: { type: 'string' }, + lastName: { type: 'string' }, + emailAddress: { type: 'string' }, + }, + }, + }, + }, + }, + responses: { + 201: { + content: { + 'application/vnd.atlas.2024-01-01+json': { + schema: { + type: 'object', + properties: { + firstName: { type: 'string' }, + lastName: { type: 'string' }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + errors: [], + }, + { + name: 'invalid examples', + document: { + components: { + schemas: { + SchemaName: { + properties: { + UserId: { type: 'string' }, + user_id: { type: 'string' }, + 'user-id': { type: 'string' }, + }, + }, + }, + }, + paths: { + '/users': { + post: { + requestBody: { + content: { + 'application/vnd.atlas.2023-01-01+json': { + schema: { + type: 'object', + properties: { + First_Name: { type: 'string' }, + lastName: { type: 'string' }, + 'Email-Address': { type: 'string' }, + }, + }, + }, + 'application/vnd.atlas.2024-01-01+json': { + schema: { + type: 'object', + properties: { + First_Name: { type: 'string' }, + lastName: { type: 'string' }, + }, + }, + }, + }, + }, + responses: { + 201: { + content: { + 'application/vnd.atlas.2024-01-01+json': { + schema: { + type: 'object', + properties: { + First_Name: { type: 'string' }, + lastName: { type: 'string' }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + errors: [ + { + code: 'xgen-IPA-112-field-names-are-camel-case', + message: 'Property "UserId" must use camelCase format.', + path: ['components', 'schemas', 'SchemaName', 'properties', 'UserId'], + severity: DiagnosticSeverity.Warning, + }, + { + code: 'xgen-IPA-112-field-names-are-camel-case', + message: 'Property "user_id" must use camelCase format.', + path: ['components', 'schemas', 'SchemaName', 'properties', 'user_id'], + severity: DiagnosticSeverity.Warning, + }, + { + code: 'xgen-IPA-112-field-names-are-camel-case', + message: 'Property "user-id" must use camelCase format.', + path: ['components', 'schemas', 'SchemaName', 'properties', 'user-id'], + severity: DiagnosticSeverity.Warning, + }, + { + code: 'xgen-IPA-112-field-names-are-camel-case', + message: 'Property "First_Name" must use camelCase format.', + path: [ + 'paths', + '/users', + 'post', + 'requestBody', + 'content', + 'application/vnd.atlas.2023-01-01+json', + 'schema', + 'properties', + 'First_Name', + ], + severity: DiagnosticSeverity.Warning, + }, + { + code: 'xgen-IPA-112-field-names-are-camel-case', + message: 'Property "Email-Address" must use camelCase format.', + path: [ + 'paths', + '/users', + 'post', + 'requestBody', + 'content', + 'application/vnd.atlas.2023-01-01+json', + 'schema', + 'properties', + 'Email-Address', + ], + severity: DiagnosticSeverity.Warning, + }, + { + code: 'xgen-IPA-112-field-names-are-camel-case', + message: 'Property "First_Name" must use camelCase format.', + path: [ + 'paths', + '/users', + 'post', + 'requestBody', + 'content', + 'application/vnd.atlas.2024-01-01+json', + 'schema', + 'properties', + 'First_Name', + ], + severity: DiagnosticSeverity.Warning, + }, + { + code: 'xgen-IPA-112-field-names-are-camel-case', + message: 'Property "First_Name" must use camelCase format.', + path: [ + 'paths', + '/users', + 'post', + 'responses', + '201', + 'content', + 'application/vnd.atlas.2024-01-01+json', + 'schema', + 'properties', + 'First_Name', + ], + severity: DiagnosticSeverity.Warning, + }, + ], + }, + { + name: 'exceptions', + document: { + components: { + schemas: { + SchemaName: { + properties: { + userId: { type: 'string' }, + 'API-Key': { + type: 'string', + 'x-xgen-IPA-exception': { + 'xgen-IPA-112-field-names-are-camel-case': 'Reason', + }, + }, + User_Name: { + type: 'string', + 'x-xgen-IPA-exception': { + 'xgen-IPA-112-field-names-are-camel-case': 'Reason', + }, + }, + }, + }, + }, + }, + paths: { + '/users': { + post: { + requestBody: { + content: { + 'application/vnd.atlas.2023-01-01+json': { + schema: { + type: 'object', + properties: { + First_Name: { + type: 'string', + 'x-xgen-IPA-exception': { + 'xgen-IPA-112-field-names-are-camel-case': 'Reason', + }, + }, + lastName: { type: 'string' }, + 'Email-Address': { + type: 'string', + 'x-xgen-IPA-exception': { + 'xgen-IPA-112-field-names-are-camel-case': 'Reason', + }, + }, + }, + }, + }, + 'application/vnd.atlas.2024-01-01+json': { + schema: { + type: 'object', + properties: { + First_Name: { + type: 'string', + 'x-xgen-IPA-exception': { + 'xgen-IPA-112-field-names-are-camel-case': 'Reason', + }, + }, + lastName: { type: 'string' }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + errors: [], + }, + { + name: 'array schema - valid field names', + document: { + components: { + schemas: { + ArraySchema: { + type: 'array', + items: { + type: 'object', + properties: { + itemId: { type: 'string' }, + itemName: { type: 'string' }, + }, + }, + }, + }, + }, + }, + errors: [], + }, + { + name: 'array schema - invalid field names', + document: { + components: { + schemas: { + ArraySchema: { + type: 'array', + items: { + type: 'object', + properties: { + Item_Id: { type: 'string' }, + 'ITEM-NAME': { type: 'string' }, + }, + }, + }, + }, + }, + }, + errors: [ + { + code: 'xgen-IPA-112-field-names-are-camel-case', + message: 'Property "Item_Id" must use camelCase format.', + path: ['components', 'schemas', 'ArraySchema', 'items', 'properties', 'Item_Id'], + severity: DiagnosticSeverity.Warning, + }, + { + code: 'xgen-IPA-112-field-names-are-camel-case', + message: 'Property "ITEM-NAME" must use camelCase format.', + path: ['components', 'schemas', 'ArraySchema', 'items', 'properties', 'ITEM-NAME'], + severity: DiagnosticSeverity.Warning, + }, + ], + }, + { + name: 'nested schema - valid field names', + document: { + components: { + schemas: { + NestedSchema: { + properties: { + userId: { type: 'string' }, + userProfile: { + type: 'object', + properties: { + firstName: { type: 'string' }, + lastName: { type: 'string' }, + contactInfo: { + type: 'object', + properties: { + emailAddress: { type: 'string' }, + phoneNumber: { type: 'string' }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + errors: [], + }, + { + name: 'nested schema - invalid field names', + document: { + components: { + schemas: { + NestedSchema: { + properties: { + userId: { type: 'string' }, + userProfile: { + type: 'object', + properties: { + First_Name: { type: 'string' }, + lastName: { type: 'string' }, + contactInfo: { + type: 'object', + properties: { + 'Email-Address': { type: 'string' }, + phoneNumber: { type: 'string' }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + errors: [ + { + code: 'xgen-IPA-112-field-names-are-camel-case', + message: 'Property "First_Name" must use camelCase format.', + path: ['components', 'schemas', 'NestedSchema', 'properties', 'userProfile', 'properties', 'First_Name'], + severity: DiagnosticSeverity.Warning, + }, + { + code: 'xgen-IPA-112-field-names-are-camel-case', + message: 'Property "Email-Address" must use camelCase format.', + path: [ + 'components', + 'schemas', + 'NestedSchema', + 'properties', + 'userProfile', + 'properties', + 'contactInfo', + 'properties', + 'Email-Address', + ], + severity: DiagnosticSeverity.Warning, + }, + ], + }, + { + name: 'mixed schema with exceptions', + document: { + components: { + schemas: { + ComplexSchema: { + properties: { + userId: { type: 'string' }, + 'API-KEY': { + type: 'string', + 'x-xgen-IPA-exception': { + 'xgen-IPA-112-field-names-are-camel-case': 'Reason', + }, + }, + userSettings: { + type: 'object', + properties: { + preferences: { + type: 'array', + items: { + type: 'object', + properties: { + settingName: { type: 'string' }, + Setting_Value: { + type: 'string', + 'x-xgen-IPA-exception': { + 'xgen-IPA-112-field-names-are-camel-case': 'UI requirement', + }, + }, + }, + }, + }, + 'DISPLAY-OPTIONS': { + type: 'object', + 'x-xgen-IPA-exception': { + 'xgen-IPA-112-field-names-are-camel-case': 'UI requirement', + }, + properties: { + theme: { type: 'string' }, + Color_Scheme: { + type: 'string', + 'x-xgen-IPA-exception': { + 'xgen-IPA-112-field-names-are-camel-case': 'UI requirement', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + errors: [], + }, +]); diff --git a/tools/spectral/ipa/rulesets/IPA-112.yaml b/tools/spectral/ipa/rulesets/IPA-112.yaml index c10298f3b7..05a647fb99 100644 --- a/tools/spectral/ipa/rulesets/IPA-112.yaml +++ b/tools/spectral/ipa/rulesets/IPA-112.yaml @@ -3,6 +3,7 @@ functions: - IPA112AvoidProjectFieldNames + - IPA112FieldNamesAreCamelCase rules: xgen-IPA-112-avoid-project-field-names: @@ -18,7 +19,10 @@ rules: - 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[*]~' + given: + - '$.components.schemas..properties[*]~' + - '$.paths..requestBody.content[?(@property.match(/json$/i))].schema..properties[*]~' + - '$.paths..responses..content[?(@property.match(/json$/i))].schema..properties[*]~' then: function: 'IPA112AvoidProjectFieldNames' functionOptions: @@ -29,3 +33,20 @@ rules: alternative: ['groups'] ignore: - 'gcp' + xgen-IPA-112-field-names-are-camel-case: + description: | + Schema field names should be in camelCase format. + + ##### Implementation details + Rule checks for the following conditions: + - Searches through all schemas in the API definition + - Identifies property names that are not in camelCase format + - Reports any instances where these field names are not in camelCase format + message: '{{error}} https://mdb.link/mongodb-atlas-openapi-validation#xgen-IPA-112-field-names-are-camel-case' + severity: warn + given: + - '$.components.schemas..properties[*]~' + - '$.paths..requestBody.content[?(@property.match(/json$/i))].schema..properties[*]~' + - '$.paths..responses..content[?(@property.match(/json$/i))].schema..properties[*]~' + then: + function: 'IPA112FieldNamesAreCamelCase' diff --git a/tools/spectral/ipa/rulesets/README.md b/tools/spectral/ipa/rulesets/README.md index 962f8d4d32..971cdda0cd 100644 --- a/tools/spectral/ipa/rulesets/README.md +++ b/tools/spectral/ipa/rulesets/README.md @@ -464,6 +464,17 @@ Rule checks for the following conditions: - Reports any instances where these field names are used - Suggests using "group", "groups", or "groupId" as alternatives +#### xgen-IPA-112-field-names-are-camel-case + + ![warn](https://img.shields.io/badge/warning-yellow) +Schema field names should be in camelCase format. + +##### Implementation details +Rule checks for the following conditions: + - Searches through all schemas in the API definition + - Identifies property names that are not in camelCase format + - Reports any instances where these field names are not in camelCase format + ### IPA-113 diff --git a/tools/spectral/ipa/rulesets/functions/IPA112FieldNamesAreCamelCase.js b/tools/spectral/ipa/rulesets/functions/IPA112FieldNamesAreCamelCase.js new file mode 100644 index 0000000000..f6d2b3c7d6 --- /dev/null +++ b/tools/spectral/ipa/rulesets/functions/IPA112FieldNamesAreCamelCase.js @@ -0,0 +1,22 @@ +import { casing } from '@stoplight/spectral-functions'; +import { collectAdoption, collectAndReturnViolation, collectException } from './utils/collectionUtils.js'; +import { hasException } from './utils/exceptions.js'; +import { resolveObject } from './utils/componentUtils.js'; + +const RULE_NAME = 'xgen-IPA-112-field-names-are-camel-case'; + +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; + } + + if (casing(input, { type: 'camel', disallowDigits: true })) { + const errorMessage = `Property "${input}" must use camelCase format.`; + return collectAndReturnViolation(path, RULE_NAME, errorMessage); + } + collectAdoption(path, RULE_NAME); +};