From 0a6fcb9105b116ec6be2d0ef2e4c173fc6ba06e5 Mon Sep 17 00:00:00 2001 From: Yeliz Henden Date: Mon, 24 Mar 2025 13:36:59 +0000 Subject: [PATCH 1/4] CLOUDP-271999: Fix on IPA-112 rule implementation --- ...A112BooleanFieldNamesAvoidIsPrefix.test.js | 143 ++++++++++++++++++ .../IPA112FieldNamesAreCamelCase.test.js | 125 +++++++++++++++ .../functions/IPA112AvoidProjectFieldNames.js | 8 +- .../IPA112BooleanFieldNamesAvoidIsPrefix.js | 10 +- .../functions/IPA112FieldNamesAreCamelCase.js | 8 +- 5 files changed, 288 insertions(+), 6 deletions(-) diff --git a/tools/spectral/ipa/__tests__/IPA112BooleanFieldNamesAvoidIsPrefix.test.js b/tools/spectral/ipa/__tests__/IPA112BooleanFieldNamesAvoidIsPrefix.test.js index fc1a654e48..4b8a0310a8 100644 --- a/tools/spectral/ipa/__tests__/IPA112BooleanFieldNamesAvoidIsPrefix.test.js +++ b/tools/spectral/ipa/__tests__/IPA112BooleanFieldNamesAvoidIsPrefix.test.js @@ -177,4 +177,147 @@ testRule('xgen-IPA-112-boolean-field-names-avoid-is-prefix', [ }, errors: [], }, + { + name: 'schema with referenced property types', + document: { + components: { + schemas: { + BooleanProperties: { + type: 'object', + properties: { + active: { type: 'boolean' }, + isEnabled: { type: 'boolean' }, + disabled: { type: 'boolean' }, + }, + }, + User: { + type: 'object', + properties: { + userId: { type: 'string' }, + name: { type: 'string' }, + status: { $ref: '#/components/schemas/BooleanProperties' }, + isAdmin: { type: 'boolean' }, + preferences: { + type: 'object', + properties: { + isEmailNotificationsEnabled: { type: 'boolean' }, + darkMode: { type: 'boolean' }, + }, + }, + }, + }, + }, + }, + paths: { + '/users/{userId}': { + get: { + responses: { + 200: { + content: { + 'application/vnd.atlas.2024-01-01+json': { + schema: { + type: 'object', + properties: { + user: { $ref: '#/components/schemas/User' }, + isCached: { type: 'boolean' }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + errors: [ + { + code: 'xgen-IPA-112-boolean-field-names-avoid-is-prefix', + message: 'Boolean field "isEnabled" should not use the "is" prefix. Use "enabled" instead.', + path: ['components', 'schemas', 'BooleanProperties', 'properties', 'isEnabled'], + severity: DiagnosticSeverity.Warning, + }, + { + code: 'xgen-IPA-112-boolean-field-names-avoid-is-prefix', + message: 'Boolean field "isAdmin" should not use the "is" prefix. Use "admin" instead.', + path: ['components', 'schemas', 'User', 'properties', 'isAdmin'], + severity: DiagnosticSeverity.Warning, + }, + { + code: 'xgen-IPA-112-boolean-field-names-avoid-is-prefix', + message: + 'Boolean field "isEmailNotificationsEnabled" should not use the "is" prefix. Use "emailNotificationsEnabled" instead.', + path: [ + 'components', + 'schemas', + 'User', + 'properties', + 'preferences', + 'properties', + 'isEmailNotificationsEnabled', + ], + severity: DiagnosticSeverity.Warning, + }, + { + code: 'xgen-IPA-112-boolean-field-names-avoid-is-prefix', + message: 'Boolean field "isCached" should not use the "is" prefix. Use "cached" instead.', + path: [ + 'paths', + '/users/{userId}', + 'get', + 'responses', + '200', + 'content', + 'application/vnd.atlas.2024-01-01+json', + 'schema', + 'properties', + 'isCached', + ], + severity: DiagnosticSeverity.Warning, + }, + ], + }, + { + name: 'schema with referenced property and exceptions', + document: { + components: { + schemas: { + UserSettings: { + type: 'object', + properties: { + isVerified: { + type: 'boolean', + 'x-xgen-IPA-exception': { + 'xgen-IPA-112-boolean-field-names-avoid-is-prefix': 'Reason', + }, + }, + isMfaEnabled: { type: 'boolean' }, + }, + }, + UserProfile: { + type: 'object', + properties: { + name: { type: 'string' }, + email: { type: 'string' }, + settings: { $ref: '#/components/schemas/UserSettings' }, + isPremiumUser: { + type: 'boolean', + 'x-xgen-IPA-exception': { + 'xgen-IPA-112-boolean-field-names-avoid-is-prefix': 'Reason', + }, + }, + }, + }, + }, + }, + }, + errors: [ + { + code: 'xgen-IPA-112-boolean-field-names-avoid-is-prefix', + message: 'Boolean field "isMfaEnabled" should not use the "is" prefix. Use "mfaEnabled" instead.', + path: ['components', 'schemas', 'UserSettings', 'properties', 'isMfaEnabled'], + severity: DiagnosticSeverity.Warning, + }, + ], + }, ]); diff --git a/tools/spectral/ipa/__tests__/IPA112FieldNamesAreCamelCase.test.js b/tools/spectral/ipa/__tests__/IPA112FieldNamesAreCamelCase.test.js index bb3c134a34..49d1ec3347 100644 --- a/tools/spectral/ipa/__tests__/IPA112FieldNamesAreCamelCase.test.js +++ b/tools/spectral/ipa/__tests__/IPA112FieldNamesAreCamelCase.test.js @@ -461,4 +461,129 @@ testRule('xgen-IPA-112-field-names-are-camel-case', [ }, errors: [], }, + { + name: 'schema with referenced property and nested objects', + document: { + components: { + schemas: { + Address: { + type: 'object', + properties: { + State_Name: { type: 'string' }, + zipCode: { type: 'string' }, + }, + }, + User: { + type: 'object', + properties: { + userId: { type: 'string' }, + firstName: { type: 'string' }, + Last_Name: { type: 'string' }, + primaryAddress: { $ref: '#/components/schemas/Address' }, + contactInfo: { + type: 'object', + properties: { + email_address: { type: 'string' }, + phoneNumber: { type: 'string' }, + }, + }, + }, + }, + }, + }, + paths: { + '/users/{userId}': { + get: { + responses: { + 200: { + content: { + 'application/vnd.atlas.2024-01-01+json': { + schema: { + type: 'object', + properties: { + user: { $ref: '#/components/schemas/User' }, + REQUEST_ID: { type: 'string' }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + errors: [ + { + code: 'xgen-IPA-112-field-names-are-camel-case', + message: 'Property "State_Name" must use camelCase format.', + path: ['components', 'schemas', 'Address', 'properties', 'State_Name'], + severity: DiagnosticSeverity.Warning, + }, + { + code: 'xgen-IPA-112-field-names-are-camel-case', + message: 'Property "Last_Name" must use camelCase format.', + path: ['components', 'schemas', 'User', 'properties', 'Last_Name'], + severity: DiagnosticSeverity.Warning, + }, + { + code: 'xgen-IPA-112-field-names-are-camel-case', + message: 'Property "email_address" must use camelCase format.', + path: ['components', 'schemas', 'User', 'properties', 'contactInfo', 'properties', 'email_address'], + severity: DiagnosticSeverity.Warning, + }, + { + code: 'xgen-IPA-112-field-names-are-camel-case', + message: 'Property "REQUEST_ID" must use camelCase format.', + path: [ + 'paths', + '/users/{userId}', + 'get', + 'responses', + '200', + 'content', + 'application/vnd.atlas.2024-01-01+json', + 'schema', + 'properties', + 'REQUEST_ID', + ], + severity: DiagnosticSeverity.Warning, + }, + ], + }, + { + name: 'schema with referenced property and exceptions', + document: { + components: { + schemas: { + ApiSettings: { + type: 'object', + properties: { + API_KEY: { + type: 'string', + 'x-xgen-IPA-exception': { + 'xgen-IPA-112-field-names-are-camel-case': 'Reason', + }, + }, + }, + }, + UserProfile: { + type: 'object', + properties: { + name: { type: 'string' }, + email: { type: 'string' }, + apiSettings: { $ref: '#/components/schemas/ApiSettings' }, + User_Status: { + type: 'string', + 'x-xgen-IPA-exception': { + 'xgen-IPA-112-field-names-are-camel-case': 'Reason', + }, + }, + }, + }, + }, + }, + }, + errors: [], + }, ]); diff --git a/tools/spectral/ipa/rulesets/functions/IPA112AvoidProjectFieldNames.js b/tools/spectral/ipa/rulesets/functions/IPA112AvoidProjectFieldNames.js index c3e0606739..9bed356e77 100644 --- a/tools/spectral/ipa/rulesets/functions/IPA112AvoidProjectFieldNames.js +++ b/tools/spectral/ipa/rulesets/functions/IPA112AvoidProjectFieldNames.js @@ -11,9 +11,15 @@ import { splitCamelCase } from './utils/schemaUtils.js'; const RULE_NAME = 'xgen-IPA-112-avoid-project-field-names'; export default (input, options, { path, documentInventory }) => { - const oas = documentInventory.resolved; + const oas = documentInventory.unresolved; const property = resolveObject(oas, path); + // Skip schema references ($ref): + // Referenced schemas are validated separately to prevent duplicate violations + if (!property) { + return; + } + const ignoreList = options?.ignore || []; if (ignoreList.some((ignoreTerm) => input.toLowerCase().includes(ignoreTerm))) { return; diff --git a/tools/spectral/ipa/rulesets/functions/IPA112BooleanFieldNamesAvoidIsPrefix.js b/tools/spectral/ipa/rulesets/functions/IPA112BooleanFieldNamesAvoidIsPrefix.js index 25f8076f75..9c9f4ca396 100644 --- a/tools/spectral/ipa/rulesets/functions/IPA112BooleanFieldNamesAvoidIsPrefix.js +++ b/tools/spectral/ipa/rulesets/functions/IPA112BooleanFieldNamesAvoidIsPrefix.js @@ -6,15 +6,17 @@ const RULE_NAME = 'xgen-IPA-112-boolean-field-names-avoid-is-prefix'; const IS_PREFIX_REGEX = /^is[A-Z]/; export default (input, options, { path, documentInventory }) => { - const oas = documentInventory.resolved; + const oas = documentInventory.unresolved; const property = resolveObject(oas, path); - if (hasException(property, RULE_NAME)) { - collectException(property, RULE_NAME, path); + // Skip schema references ($ref) and non-boolean fields: + // Referenced schemas are validated separately to prevent duplicate violations + if (!property || property.type !== 'boolean') { return; } - if (property.type !== 'boolean') { + if (hasException(property, RULE_NAME)) { + collectException(property, RULE_NAME, path); return; } diff --git a/tools/spectral/ipa/rulesets/functions/IPA112FieldNamesAreCamelCase.js b/tools/spectral/ipa/rulesets/functions/IPA112FieldNamesAreCamelCase.js index f6d2b3c7d6..c96fe48d59 100644 --- a/tools/spectral/ipa/rulesets/functions/IPA112FieldNamesAreCamelCase.js +++ b/tools/spectral/ipa/rulesets/functions/IPA112FieldNamesAreCamelCase.js @@ -6,9 +6,15 @@ 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 oas = documentInventory.unresolved; const property = resolveObject(oas, path); + // Skip schema references ($ref): + // Referenced schemas are validated separately to prevent duplicate violations + if (!property) { + return; + } + if (hasException(property, RULE_NAME)) { collectException(property, RULE_NAME, path); return; From 4ce7f0c81ee5b699f1a8715a07cab234cbc1bc3c Mon Sep 17 00:00:00 2001 From: Yeliz Henden Date: Mon, 24 Mar 2025 13:55:29 +0000 Subject: [PATCH 2/4] fix --- .../IPA112FieldNamesAreCamelCase.test.js | 27 +++++++++++++++++-- tools/spectral/ipa/rulesets/IPA-112.yaml | 21 ++++++++------- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/tools/spectral/ipa/__tests__/IPA112FieldNamesAreCamelCase.test.js b/tools/spectral/ipa/__tests__/IPA112FieldNamesAreCamelCase.test.js index 49d1ec3347..95b3ffbfd6 100644 --- a/tools/spectral/ipa/__tests__/IPA112FieldNamesAreCamelCase.test.js +++ b/tools/spectral/ipa/__tests__/IPA112FieldNamesAreCamelCase.test.js @@ -479,7 +479,7 @@ testRule('xgen-IPA-112-field-names-are-camel-case', [ userId: { type: 'string' }, firstName: { type: 'string' }, Last_Name: { type: 'string' }, - primaryAddress: { $ref: '#/components/schemas/Address' }, + primary_address: { $ref: '#/components/schemas/Address' }, contactInfo: { type: 'object', properties: { @@ -501,7 +501,7 @@ testRule('xgen-IPA-112-field-names-are-camel-case', [ schema: { type: 'object', properties: { - user: { $ref: '#/components/schemas/User' }, + primary_user: { $ref: '#/components/schemas/User' }, REQUEST_ID: { type: 'string' }, }, }, @@ -526,12 +526,35 @@ testRule('xgen-IPA-112-field-names-are-camel-case', [ path: ['components', 'schemas', 'User', 'properties', 'Last_Name'], severity: DiagnosticSeverity.Warning, }, + { + code: 'xgen-IPA-112-field-names-are-camel-case', + message: 'Property "primary_address" must use camelCase format.', + path: ['components', 'schemas', 'User', 'properties', 'primary_address'], + severity: DiagnosticSeverity.Warning, + }, { code: 'xgen-IPA-112-field-names-are-camel-case', message: 'Property "email_address" must use camelCase format.', path: ['components', 'schemas', 'User', 'properties', 'contactInfo', 'properties', 'email_address'], severity: DiagnosticSeverity.Warning, }, + { + code: 'xgen-IPA-112-field-names-are-camel-case', + message: 'Property "primary_user" must use camelCase format.', + path: [ + 'paths', + '/users/{userId}', + 'get', + 'responses', + '200', + 'content', + 'application/vnd.atlas.2024-01-01+json', + 'schema', + 'properties', + 'primary_user', + ], + severity: DiagnosticSeverity.Warning, + }, { code: 'xgen-IPA-112-field-names-are-camel-case', message: 'Property "REQUEST_ID" must use camelCase format.', diff --git a/tools/spectral/ipa/rulesets/IPA-112.yaml b/tools/spectral/ipa/rulesets/IPA-112.yaml index ca484fff11..d2a5f0ea21 100644 --- a/tools/spectral/ipa/rulesets/IPA-112.yaml +++ b/tools/spectral/ipa/rulesets/IPA-112.yaml @@ -21,10 +21,11 @@ rules: message: '{{error}} https://mdb.link/mongodb-atlas-openapi-validation#xgen-IPA-112-avoid-project-field-names' severity: warn given: - - '$.components.schemas..properties[*]~' - - '$.paths..requestBody.content[?(@property.match(/json$/i))].schema..properties[*]~' - - '$.paths..responses..content[?(@property.match(/json$/i))].schema..properties[*]~' + - '$.components.schemas..properties' + - '$.paths..requestBody.content[?(@property.match(/json$/i))].schema..properties' + - '$.paths..responses..content[?(@property.match(/json$/i))].schema..properties' then: + field: '@key' function: 'IPA112AvoidProjectFieldNames' functionOptions: prohibitedFieldNames: @@ -46,10 +47,11 @@ rules: 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[*]~' + - '$.components.schemas..properties' + - '$.paths..requestBody.content[?(@property.match(/json$/i))].schema..properties' + - '$.paths..responses..content[?(@property.match(/json$/i))].schema..properties' then: + field: '@key' function: 'IPA112FieldNamesAreCamelCase' xgen-IPA-112-boolean-field-names-avoid-is-prefix: description: | @@ -63,8 +65,9 @@ rules: message: '{{error}} https://mdb.link/mongodb-atlas-openapi-validation#xgen-IPA-112-boolean-field-names-avoid-is-prefix' severity: warn given: - - '$.components.schemas..properties[*]~' - - '$.paths..requestBody.content[?(@property.match(/json$/i))].schema..properties[*]~' - - '$.paths..responses..content[?(@property.match(/json$/i))].schema..properties[*]~' + - '$.components.schemas..properties' + - '$.paths..requestBody.content[?(@property.match(/json$/i))].schema..properties' + - '$.paths..responses..content[?(@property.match(/json$/i))].schema..properties' then: + field: '@key' function: 'IPA112BooleanFieldNamesAvoidIsPrefix' From 694e2daf61c274d4f86dfb0605ad2fbeae9199d3 Mon Sep 17 00:00:00 2001 From: Yeliz Henden Date: Mon, 24 Mar 2025 15:10:11 +0000 Subject: [PATCH 3/4] fix --- .../IPA112BooleanFieldNamesAvoidIsPrefix.js | 28 +++++++++++++---- .../functions/IPA112FieldNamesAreCamelCase.js | 30 ++++++++++++++++--- 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/tools/spectral/ipa/rulesets/functions/IPA112BooleanFieldNamesAvoidIsPrefix.js b/tools/spectral/ipa/rulesets/functions/IPA112BooleanFieldNamesAvoidIsPrefix.js index 9c9f4ca396..41b582dd93 100644 --- a/tools/spectral/ipa/rulesets/functions/IPA112BooleanFieldNamesAvoidIsPrefix.js +++ b/tools/spectral/ipa/rulesets/functions/IPA112BooleanFieldNamesAvoidIsPrefix.js @@ -1,4 +1,9 @@ -import { collectAdoption, collectAndReturnViolation, collectException } from './utils/collectionUtils.js'; +import { + collectAdoption, + collectAndReturnViolation, + collectException, + handleInternalError, +} from './utils/collectionUtils.js'; import { hasException } from './utils/exceptions.js'; import { resolveObject } from './utils/componentUtils.js'; @@ -20,11 +25,22 @@ export default (input, options, { path, documentInventory }) => { return; } - if (IS_PREFIX_REGEX.test(input)) { - const suggestedName = input.charAt(2).toLowerCase() + input.slice(3); - const errorMessage = `Boolean field "${input}" should not use the "is" prefix. Use "${suggestedName}" instead.`; - return collectAndReturnViolation(path, RULE_NAME, errorMessage); + const errors = checkViolationsAndReturnErrors(input, path); + if (errors.length !== 0) { + return collectAndReturnViolation(path, RULE_NAME, errors); } - collectAdoption(path, RULE_NAME); }; + +function checkViolationsAndReturnErrors(input, path) { + try { + if (IS_PREFIX_REGEX.test(input)) { + const suggestedName = input.charAt(2).toLowerCase() + input.slice(3); + const errorMessage = `Boolean field "${input}" should not use the "is" prefix. Use "${suggestedName}" instead.`; + return [{ path, message: errorMessage }]; + } + return []; + } catch (e) { + handleInternalError(RULE_NAME, path, e); + } +} diff --git a/tools/spectral/ipa/rulesets/functions/IPA112FieldNamesAreCamelCase.js b/tools/spectral/ipa/rulesets/functions/IPA112FieldNamesAreCamelCase.js index c96fe48d59..3586a0d160 100644 --- a/tools/spectral/ipa/rulesets/functions/IPA112FieldNamesAreCamelCase.js +++ b/tools/spectral/ipa/rulesets/functions/IPA112FieldNamesAreCamelCase.js @@ -1,5 +1,10 @@ import { casing } from '@stoplight/spectral-functions'; -import { collectAdoption, collectAndReturnViolation, collectException } from './utils/collectionUtils.js'; +import { + collectAdoption, + collectAndReturnViolation, + collectException, + handleInternalError, +} from './utils/collectionUtils.js'; import { hasException } from './utils/exceptions.js'; import { resolveObject } from './utils/componentUtils.js'; @@ -15,14 +20,31 @@ export default (input, options, { path, documentInventory }) => { return; } + if (input === 'mongoDBEmployeeAccessGrant') { + console.log(property); + console.log(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); + const errors = checkViolationsAndReturnErrors(input, path); + if (errors.length !== 0) { + return collectAndReturnViolation(path, RULE_NAME, errors); } collectAdoption(path, RULE_NAME); }; + +function checkViolationsAndReturnErrors(input, path) { + try { + if (casing(input, { type: 'camel', disallowDigits: true })) { + const errorMessage = `Property "${input}" must use camelCase format.`; + return [{ path, message: errorMessage }]; + } + return []; + } catch (e) { + handleInternalError(RULE_NAME, path, e); + } +} From 4c8b3df8986a70bcf63b2b33fe5e2a40f826c3f6 Mon Sep 17 00:00:00 2001 From: Yeliz Henden Date: Mon, 24 Mar 2025 15:12:58 +0000 Subject: [PATCH 4/4] remove log lines --- .../ipa/rulesets/functions/IPA112FieldNamesAreCamelCase.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tools/spectral/ipa/rulesets/functions/IPA112FieldNamesAreCamelCase.js b/tools/spectral/ipa/rulesets/functions/IPA112FieldNamesAreCamelCase.js index 3586a0d160..4ba51a4054 100644 --- a/tools/spectral/ipa/rulesets/functions/IPA112FieldNamesAreCamelCase.js +++ b/tools/spectral/ipa/rulesets/functions/IPA112FieldNamesAreCamelCase.js @@ -20,11 +20,6 @@ export default (input, options, { path, documentInventory }) => { return; } - if (input === 'mongoDBEmployeeAccessGrant') { - console.log(property); - console.log(path); - } - if (hasException(property, RULE_NAME)) { collectException(property, RULE_NAME, path); return;