From 48b063353b7ab3972924bcee55a582b6f0544b48 Mon Sep 17 00:00:00 2001 From: Yeliz Henden Date: Thu, 19 Dec 2024 14:59:05 +0000 Subject: [PATCH 1/8] CLOUDP-287247: IPA-109: Validate custom method must use colon (:) followed by the custom verb --- package-lock.json | 1 + package.json | 1 + .../eachCustomMethodMustBeGetOrPost.test.js | 22 ++++++ .../eachCustomMethodMustUseCamelCase.test.js | 70 +++++++++++++++++++ tools/spectral/ipa/rulesets/IPA-109.yaml | 9 +++ .../eachCustomMethodMustBeGetOrPost.js | 6 ++ .../eachCustomMethodMustUseCamelCase.js | 34 +++++++++ .../functions/utils/resourceEvaluation.js | 5 ++ 8 files changed, 148 insertions(+) create mode 100644 tools/spectral/ipa/__tests__/eachCustomMethodMustUseCamelCase.test.js create mode 100644 tools/spectral/ipa/rulesets/functions/eachCustomMethodMustUseCamelCase.js diff --git a/package-lock.json b/package-lock.json index 2af7a44a36..e47eeb40e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "dependencies": { "@stoplight/spectral-cli": "^6.14.2", "@stoplight/spectral-core": "^1.19.4", + "@stoplight/spectral-functions": "^1.9.3", "@stoplight/spectral-ref-resolver": "^1.0.5", "@stoplight/spectral-ruleset-bundler": "^1.6.1", "eslint-plugin-jest": "^28.9.0", diff --git a/package.json b/package.json index 8744a4a3ff..ebe70edb00 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "dependencies": { "@stoplight/spectral-cli": "^6.14.2", "@stoplight/spectral-core": "^1.19.4", + "@stoplight/spectral-functions": "^1.9.3", "@stoplight/spectral-ref-resolver": "^1.0.5", "@stoplight/spectral-ruleset-bundler": "^1.6.1", "eslint-plugin-jest": "^28.9.0", diff --git a/tools/spectral/ipa/__tests__/eachCustomMethodMustBeGetOrPost.test.js b/tools/spectral/ipa/__tests__/eachCustomMethodMustBeGetOrPost.test.js index 369f61efcd..13d4e764ea 100644 --- a/tools/spectral/ipa/__tests__/eachCustomMethodMustBeGetOrPost.test.js +++ b/tools/spectral/ipa/__tests__/eachCustomMethodMustBeGetOrPost.test.js @@ -30,6 +30,28 @@ testRule('xgen-IPA-109-custom-method-must-be-GET-or-POST', [ }, errors: [], }, + { + name: 'invalid methods with exception', + document: { + paths: { + '/d/{exampleId}:method': { + get: {}, + post: {}, + 'x-xgen-IPA-exception': { + 'xgen-IPA-109-custom-method-must-be-GET-or-POST' : {} + }, + }, + '/d:method': { + get: {}, + post: {}, + 'x-xgen-IPA-exception': { + 'xgen-IPA-109-custom-method-must-be-GET-or-POST' : {} + }, + }, + }, + }, + errors: [], + }, { name: 'invalid methods', document: { diff --git a/tools/spectral/ipa/__tests__/eachCustomMethodMustUseCamelCase.test.js b/tools/spectral/ipa/__tests__/eachCustomMethodMustUseCamelCase.test.js new file mode 100644 index 0000000000..8214f5fa90 --- /dev/null +++ b/tools/spectral/ipa/__tests__/eachCustomMethodMustUseCamelCase.test.js @@ -0,0 +1,70 @@ +import testRule from './__helpers__/testRule'; +import { DiagnosticSeverity } from '@stoplight/types'; + +testRule('xgen-IPA-109-custom-method-must-use-camel-case', [ + { + name: 'valid methods', + document: { + paths: { + '/a/{exampleId}:methodName': {}, + '/a:methodName': {}, + }, + }, + errors: [], + }, + { + name: 'invalid methods with exception', + document: { + paths: { + '/b/{exampleId}:MethodName': { + 'x-xgen-IPA-exception': { + 'xgen-IPA-109-custom-method-must-use-camel-case' : {} + }, + }, + '/b:MethodName': { + 'x-xgen-IPA-exception': { + 'xgen-IPA-109-custom-method-must-use-camel-case' : {} + }, + }, + }, + }, + errors: [], + }, + { + name: 'invalid methods', + document: { + paths: { + '/a/{exampleId}:MethodName': {}, + '/a:MethodName': {}, + '/a/{exampleId}:method_name': {}, + '/a:method_name': {}, + }, + }, + errors: [ + { + code: 'xgen-IPA-109-custom-method-must-use-camel-case', + message: 'The custom method must use camelCase format. http://go/ipa/109', + path: ['paths', '/a/{exampleId}:MethodName'], + severity: DiagnosticSeverity.Warning, + }, + { + code: 'xgen-IPA-109-custom-method-must-use-camel-case', + message: 'The custom method must use camelCase format. http://go/ipa/109', + path: ['paths', '/a:MethodName'], + severity: DiagnosticSeverity.Warning, + }, + { + code: 'xgen-IPA-109-custom-method-must-use-camel-case', + message: 'The custom method must use camelCase format. http://go/ipa/109', + path: ['paths', '/a/{exampleId}:method_name'], + severity: DiagnosticSeverity.Warning, + }, + { + code: 'xgen-IPA-109-custom-method-must-use-camel-case', + message: 'The custom method must use camelCase format. http://go/ipa/109', + path: ['paths', '/a:method_name'], + severity: DiagnosticSeverity.Warning, + } + ], + }, +]); diff --git a/tools/spectral/ipa/rulesets/IPA-109.yaml b/tools/spectral/ipa/rulesets/IPA-109.yaml index 48c1645e07..dd8185b7fa 100644 --- a/tools/spectral/ipa/rulesets/IPA-109.yaml +++ b/tools/spectral/ipa/rulesets/IPA-109.yaml @@ -3,6 +3,7 @@ functions: - eachCustomMethodMustBeGetOrPost + - eachCustomMethodMustUseCamelCase rules: xgen-IPA-109-custom-method-must-be-GET-or-POST: @@ -12,3 +13,11 @@ rules: given: '$.paths[*]' then: function: 'eachCustomMethodMustBeGetOrPost' + + xgen-IPA-109-custom-method-must-use-camel-case: + description: 'The custom method must use camelCase format. http://go/ipa/109' + message: '{{error}} http://go/ipa/109' + severity: warn + given: '$.paths[*]' + then: + function: 'eachCustomMethodMustUseCamelCase' \ No newline at end of file diff --git a/tools/spectral/ipa/rulesets/functions/eachCustomMethodMustBeGetOrPost.js b/tools/spectral/ipa/rulesets/functions/eachCustomMethodMustBeGetOrPost.js index bb7be025da..fa14929c6f 100644 --- a/tools/spectral/ipa/rulesets/functions/eachCustomMethodMustBeGetOrPost.js +++ b/tools/spectral/ipa/rulesets/functions/eachCustomMethodMustBeGetOrPost.js @@ -1,5 +1,7 @@ import { isCustomMethod } from './utils/resourceEvaluation.js'; +import { hasException } from './utils/exceptions.js'; +const RULE_NAME = 'xgen-IPA-109-custom-method-must-be-GET-or-POST'; const ERROR_MESSAGE = 'The HTTP method for custom methods must be GET or POST.'; const ERROR_RESULT = [{ message: ERROR_MESSAGE }]; const VALID_METHODS = ['get', 'post']; @@ -11,6 +13,10 @@ export default (input, opts, { path }) => { if (!isCustomMethod(pathKey)) return; + if (hasException(input, RULE_NAME)) { + return; + } + //Extract the keys which are equivalent of the http methods let keys = Object.keys(input); const httpMethods = keys.filter((key) => HTTP_METHODS.includes(key)); diff --git a/tools/spectral/ipa/rulesets/functions/eachCustomMethodMustUseCamelCase.js b/tools/spectral/ipa/rulesets/functions/eachCustomMethodMustUseCamelCase.js new file mode 100644 index 0000000000..2a2a4ca1f0 --- /dev/null +++ b/tools/spectral/ipa/rulesets/functions/eachCustomMethodMustUseCamelCase.js @@ -0,0 +1,34 @@ +import { getCustomMethod, isCustomMethod } from './utils/resourceEvaluation.js'; +import { hasException } from './utils/exceptions.js'; +import { casing } from '@stoplight/spectral-functions'; + +const RULE_NAME = 'xgen-IPA-109-custom-method-must-use-camel-case'; +const ERROR_MESSAGE = 'The custom method must use camelCase format.'; +const ERROR_RESULT = [{ message: ERROR_MESSAGE }]; + +export default (input, opts, { path }) => { + // Extract the path key (e.g., '/a/{exampleId}:method') from the JSONPath. + let pathKey = path[1]; + + if (!isCustomMethod(pathKey)) return; + + if (hasException(input, RULE_NAME)) { + return; + } + + let methodName = getCustomMethod(pathKey); + if (!methodName) { + return; + } + + const isCamelCase = casing('fooBar' , { type: 'camel', disallowDigits: true }); + console.log(isCamelCase); + if (!isCamelCase) { + return ERROR_RESULT; + } + + /*const camelCaseRegex = /^[a-z]+(?:[A-Z](?:[a-z]+|$))*$/; + if (!camelCaseRegex.test(methodName)) { + return ERROR_RESULT; + }*/ +}; diff --git a/tools/spectral/ipa/rulesets/functions/utils/resourceEvaluation.js b/tools/spectral/ipa/rulesets/functions/utils/resourceEvaluation.js index 0c7d61e0e1..c8f0806921 100644 --- a/tools/spectral/ipa/rulesets/functions/utils/resourceEvaluation.js +++ b/tools/spectral/ipa/rulesets/functions/utils/resourceEvaluation.js @@ -6,6 +6,11 @@ export function isCustomMethod(path) { return path.includes(':'); } +export function getCustomMethod(path) { + const [, methodName] = path.split(':'); + return methodName; +} + /** * Checks if a resource is a singleton resource ({@link https://docs.devprod.prod.corp.mongodb.com/ipa/113 IPA-113}) based on the paths for the * resource. The resource may have custom methods. Use {@link getResourcePaths} to get all paths of a resource. From a8ec29f26e49ec92c174d9ee516dcdf514f243fb Mon Sep 17 00:00:00 2001 From: Yeliz Henden Date: Thu, 19 Dec 2024 15:01:16 +0000 Subject: [PATCH 2/8] prettier fix --- .../ipa/__tests__/eachCustomMethodMustBeGetOrPost.test.js | 4 ++-- .../ipa/__tests__/eachCustomMethodMustUseCamelCase.test.js | 6 +++--- tools/spectral/ipa/rulesets/IPA-109.yaml | 2 +- .../rulesets/functions/eachCustomMethodMustUseCamelCase.js | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tools/spectral/ipa/__tests__/eachCustomMethodMustBeGetOrPost.test.js b/tools/spectral/ipa/__tests__/eachCustomMethodMustBeGetOrPost.test.js index 13d4e764ea..ec919c3e94 100644 --- a/tools/spectral/ipa/__tests__/eachCustomMethodMustBeGetOrPost.test.js +++ b/tools/spectral/ipa/__tests__/eachCustomMethodMustBeGetOrPost.test.js @@ -38,14 +38,14 @@ testRule('xgen-IPA-109-custom-method-must-be-GET-or-POST', [ get: {}, post: {}, 'x-xgen-IPA-exception': { - 'xgen-IPA-109-custom-method-must-be-GET-or-POST' : {} + 'xgen-IPA-109-custom-method-must-be-GET-or-POST': {}, }, }, '/d:method': { get: {}, post: {}, 'x-xgen-IPA-exception': { - 'xgen-IPA-109-custom-method-must-be-GET-or-POST' : {} + 'xgen-IPA-109-custom-method-must-be-GET-or-POST': {}, }, }, }, diff --git a/tools/spectral/ipa/__tests__/eachCustomMethodMustUseCamelCase.test.js b/tools/spectral/ipa/__tests__/eachCustomMethodMustUseCamelCase.test.js index 8214f5fa90..84ac37ccb3 100644 --- a/tools/spectral/ipa/__tests__/eachCustomMethodMustUseCamelCase.test.js +++ b/tools/spectral/ipa/__tests__/eachCustomMethodMustUseCamelCase.test.js @@ -18,12 +18,12 @@ testRule('xgen-IPA-109-custom-method-must-use-camel-case', [ paths: { '/b/{exampleId}:MethodName': { 'x-xgen-IPA-exception': { - 'xgen-IPA-109-custom-method-must-use-camel-case' : {} + 'xgen-IPA-109-custom-method-must-use-camel-case': {}, }, }, '/b:MethodName': { 'x-xgen-IPA-exception': { - 'xgen-IPA-109-custom-method-must-use-camel-case' : {} + 'xgen-IPA-109-custom-method-must-use-camel-case': {}, }, }, }, @@ -64,7 +64,7 @@ testRule('xgen-IPA-109-custom-method-must-use-camel-case', [ message: 'The custom method must use camelCase format. http://go/ipa/109', path: ['paths', '/a:method_name'], severity: DiagnosticSeverity.Warning, - } + }, ], }, ]); diff --git a/tools/spectral/ipa/rulesets/IPA-109.yaml b/tools/spectral/ipa/rulesets/IPA-109.yaml index dd8185b7fa..5d3561f386 100644 --- a/tools/spectral/ipa/rulesets/IPA-109.yaml +++ b/tools/spectral/ipa/rulesets/IPA-109.yaml @@ -20,4 +20,4 @@ rules: severity: warn given: '$.paths[*]' then: - function: 'eachCustomMethodMustUseCamelCase' \ No newline at end of file + function: 'eachCustomMethodMustUseCamelCase' diff --git a/tools/spectral/ipa/rulesets/functions/eachCustomMethodMustUseCamelCase.js b/tools/spectral/ipa/rulesets/functions/eachCustomMethodMustUseCamelCase.js index 2a2a4ca1f0..951b0c7a6e 100644 --- a/tools/spectral/ipa/rulesets/functions/eachCustomMethodMustUseCamelCase.js +++ b/tools/spectral/ipa/rulesets/functions/eachCustomMethodMustUseCamelCase.js @@ -21,7 +21,7 @@ export default (input, opts, { path }) => { return; } - const isCamelCase = casing('fooBar' , { type: 'camel', disallowDigits: true }); + const isCamelCase = casing('fooBar', { type: 'camel', disallowDigits: true }); console.log(isCamelCase); if (!isCamelCase) { return ERROR_RESULT; From 9a7fc40b98796b6b19f2ec87654dc1e36dec903d Mon Sep 17 00:00:00 2001 From: Yeliz Henden Date: Thu, 19 Dec 2024 15:08:58 +0000 Subject: [PATCH 3/8] fix --- .../functions/eachCustomMethodMustUseCamelCase.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/tools/spectral/ipa/rulesets/functions/eachCustomMethodMustUseCamelCase.js b/tools/spectral/ipa/rulesets/functions/eachCustomMethodMustUseCamelCase.js index 951b0c7a6e..f866e88693 100644 --- a/tools/spectral/ipa/rulesets/functions/eachCustomMethodMustUseCamelCase.js +++ b/tools/spectral/ipa/rulesets/functions/eachCustomMethodMustUseCamelCase.js @@ -21,14 +21,8 @@ export default (input, opts, { path }) => { return; } - const isCamelCase = casing('fooBar', { type: 'camel', disallowDigits: true }); - console.log(isCamelCase); - if (!isCamelCase) { + const isCamelCase = casing(methodName, { type: 'camel', disallowDigits: true }); + if (isCamelCase !== undefined) { return ERROR_RESULT; } - - /*const camelCaseRegex = /^[a-z]+(?:[A-Z](?:[a-z]+|$))*$/; - if (!camelCaseRegex.test(methodName)) { - return ERROR_RESULT; - }*/ }; From 49de978dff31aa7c5d6d8b19c83d714ddce56ae2 Mon Sep 17 00:00:00 2001 From: Yeliz Henden Date: Fri, 20 Dec 2024 09:45:50 +0000 Subject: [PATCH 4/8] CLOUDP-287249: IPA-123: Validate enums must be UPPER_SNAKE_CASE --- .../eachEnumValueMustBeUpperSnakeCase.test.js | 164 ++++++++++++++++++ tools/spectral/ipa/ipa-spectral.yaml | 1 + tools/spectral/ipa/rulesets/IPA-123.yaml | 14 ++ .../eachEnumValueMustBeUpperSnakeCase.js | 37 ++++ 4 files changed, 216 insertions(+) create mode 100644 tools/spectral/ipa/__tests__/eachEnumValueMustBeUpperSnakeCase.test.js create mode 100644 tools/spectral/ipa/rulesets/IPA-123.yaml create mode 100644 tools/spectral/ipa/rulesets/functions/eachEnumValueMustBeUpperSnakeCase.js diff --git a/tools/spectral/ipa/__tests__/eachEnumValueMustBeUpperSnakeCase.test.js b/tools/spectral/ipa/__tests__/eachEnumValueMustBeUpperSnakeCase.test.js new file mode 100644 index 0000000000..4e6bbbfc81 --- /dev/null +++ b/tools/spectral/ipa/__tests__/eachEnumValueMustBeUpperSnakeCase.test.js @@ -0,0 +1,164 @@ +import testRule from './__helpers__/testRule'; +import { DiagnosticSeverity } from '@stoplight/types'; + +testRule('xgen-IPA-123-enum-values-must-be-upper-snake-case', [ + { + name: 'valid schema - components.schemas', + document: { + components: { + schemas: { + SchemaName: { + properties: { + exampleProperty: { + enum: ['EXAMPLE_A', 'EXAMPLE_B'], + type: 'string', + }, + }, + }, + }, + }, + }, + errors: [], + }, + { + name: 'invalid schema with exception - components.schemas', + document: { + components: { + schemas: { + SchemaName: { + 'x-xgen-IPA-exception': { + 'xgen-IPA-123-enum-values-must-be-upper-snake-case' : {} + }, + properties: { + exampleProperty: { + enum: ['exampleA', 'exampleB'], + type: 'string', + }, + }, + }, + }, + }, + }, + errors: [], + }, + { + name: 'invalid schema with exception - components.schemas', + document: { + components: { + schemas: { + SchemaName: { + properties: { + exampleProperty: { + enum: ['exampleA', 'exampleB'], + type: 'string', + }, + }, + }, + }, + }, + }, + errors: [ + { + code: 'xgen-IPA-123-enum-values-must-be-upper-snake-case', + message: 'exampleA enum value must be UPPER_SNAKE_CASE. http://go/ipa/123', + path: ['components','schemas', 'SchemaName', 'properties', 'exampleProperty', 'enum'], + severity: DiagnosticSeverity.Warning, + }, + { + code: 'xgen-IPA-123-enum-values-must-be-upper-snake-case', + message: 'exampleB enum value must be UPPER_SNAKE_CASE. http://go/ipa/123', + path: ['components','schemas', 'SchemaName', 'properties', 'exampleProperty', 'enum'], + severity: DiagnosticSeverity.Warning, + } + ], + }, + { + name: 'valid schema - paths.*', + document: { + paths: { + '/a/{exampleId}': { + get: { + parameters: [ + { + schema: { + type: 'string', + enum: ['EXAMPLE_A', 'EXAMPLE_B'], + }, + }, + ], + }, + } + }, + }, + errors: [], + }, + { + name: 'invalid schema with exceptions - paths.*', + document: { + paths: { + '/a/{exampleId}': { + get: { + parameters: [ + { + schema: { + 'x-xgen-IPA-exception': { + 'xgen-IPA-123-enum-values-must-be-upper-snake-case' : {} + }, + type: 'string', + enum: ['exampleA', 'exampleB'], + }, + }, + ], + }, + } + }, + }, + errors: [ + { + code: 'xgen-IPA-123-enum-values-must-be-upper-snake-case', + message: 'exampleA enum value must be UPPER_SNAKE_CASE. http://go/ipa/123', + path: ['paths','/a/{exampleId}', 'get', 'parameters', '0', 'schema', 'enum'], + severity: DiagnosticSeverity.Warning, + }, + { + code: 'xgen-IPA-123-enum-values-must-be-upper-snake-case', + message: 'exampleB enum value must be UPPER_SNAKE_CASE. http://go/ipa/123', + path: ['paths','/a/{exampleId}', 'get', 'parameters', '0', 'schema', 'enum'], + severity: DiagnosticSeverity.Warning, + } + ], + }, + { + name: 'invalid schema - paths.*', + document: { + paths: { + '/a/{exampleId}': { + get: { + parameters: [ + { + schema: { + type: 'string', + enum: ['exampleA', 'exampleB'], + }, + }, + ], + }, + } + }, + }, + errors: [ + { + code: 'xgen-IPA-123-enum-values-must-be-upper-snake-case', + message: 'exampleA enum value must be UPPER_SNAKE_CASE. http://go/ipa/123', + path: ['paths','/a/{exampleId}', 'get', 'parameters', '0', 'schema', 'enum'], + severity: DiagnosticSeverity.Warning, + }, + { + code: 'xgen-IPA-123-enum-values-must-be-upper-snake-case', + message: 'exampleB enum value must be UPPER_SNAKE_CASE. http://go/ipa/123', + path: ['paths','/a/{exampleId}', 'get', 'parameters', '0', 'schema', 'enum'], + severity: DiagnosticSeverity.Warning, + } + ], + }, +]); diff --git a/tools/spectral/ipa/ipa-spectral.yaml b/tools/spectral/ipa/ipa-spectral.yaml index 789367fc96..204b0e2db0 100644 --- a/tools/spectral/ipa/ipa-spectral.yaml +++ b/tools/spectral/ipa/ipa-spectral.yaml @@ -3,3 +3,4 @@ extends: - ./rulesets/IPA-102.yaml - ./rulesets/IPA-104.yaml - ./rulesets/IPA-109.yaml + - ./rulesets/IPA-123.yaml diff --git a/tools/spectral/ipa/rulesets/IPA-123.yaml b/tools/spectral/ipa/rulesets/IPA-123.yaml new file mode 100644 index 0000000000..23b9c66223 --- /dev/null +++ b/tools/spectral/ipa/rulesets/IPA-123.yaml @@ -0,0 +1,14 @@ +# IPA-123: Enums +# http://go/ipa/123 + +functions: + - eachEnumValueMustBeUpperSnakeCase + +rules: + xgen-IPA-123-enum-values-must-be-upper-snake-case: + description: 'Enum values must be UPPER_SNAKE_CASE. http://go/ipa/123' + message: '{{error}} http://go/ipa/123' + severity: warn + given: '$..enum' + then: + function: 'eachEnumValueMustBeUpperSnakeCase' diff --git a/tools/spectral/ipa/rulesets/functions/eachEnumValueMustBeUpperSnakeCase.js b/tools/spectral/ipa/rulesets/functions/eachEnumValueMustBeUpperSnakeCase.js new file mode 100644 index 0000000000..d64d920984 --- /dev/null +++ b/tools/spectral/ipa/rulesets/functions/eachEnumValueMustBeUpperSnakeCase.js @@ -0,0 +1,37 @@ +import { hasException } from './utils/exceptions.js'; +import { casing } from '@stoplight/spectral-functions'; + +const RULE_NAME = 'xgen-IPA-123-enum-values-must-be-upper-snake-case'; +const ERROR_MESSAGE = 'enum value must be UPPER_SNAKE_CASE.'; + +function getSchemaPath(path) { + if (path.includes('components')) { + return path.slice(0, 3); + } else if (path.includes('paths')) { + const index = path.findIndex((item) => item === 'schema'); + return path.slice(0, index + 1); + } +} + +export default (input, _, {path}) => { + //There are two path types for enum definition + //First type: components.schemas.schemaName.*.enum + //Second type: paths.*.method.parameters[*].schema.enum + if(hasException(getSchemaPath(path), RULE_NAME)) { + return; + } + + const errors = []; + for (const enumValue of input) { + const isUpperSnakeCase = casing(enumValue, { type: 'macro' }); + + if (isUpperSnakeCase) { + errors.push({ + message: `${enumValue} ${ERROR_MESSAGE} `, + path: path.concat(enumValue), + }); + } + } + + return errors; +}; From 0dc79a52b270d8abcbbf184cb72ed84f94eae699 Mon Sep 17 00:00:00 2001 From: Yeliz Henden Date: Fri, 20 Dec 2024 09:46:11 +0000 Subject: [PATCH 5/8] CLOUDP-287249: IPA-123: Validate enums must be UPPER_SNAKE_CASE --- .../eachEnumValueMustBeUpperSnakeCase.test.js | 28 +++++++++---------- .../eachEnumValueMustBeUpperSnakeCase.js | 4 +-- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/tools/spectral/ipa/__tests__/eachEnumValueMustBeUpperSnakeCase.test.js b/tools/spectral/ipa/__tests__/eachEnumValueMustBeUpperSnakeCase.test.js index 4e6bbbfc81..1b9edbb7e9 100644 --- a/tools/spectral/ipa/__tests__/eachEnumValueMustBeUpperSnakeCase.test.js +++ b/tools/spectral/ipa/__tests__/eachEnumValueMustBeUpperSnakeCase.test.js @@ -27,7 +27,7 @@ testRule('xgen-IPA-123-enum-values-must-be-upper-snake-case', [ schemas: { SchemaName: { 'x-xgen-IPA-exception': { - 'xgen-IPA-123-enum-values-must-be-upper-snake-case' : {} + 'xgen-IPA-123-enum-values-must-be-upper-snake-case': {}, }, properties: { exampleProperty: { @@ -61,15 +61,15 @@ testRule('xgen-IPA-123-enum-values-must-be-upper-snake-case', [ { code: 'xgen-IPA-123-enum-values-must-be-upper-snake-case', message: 'exampleA enum value must be UPPER_SNAKE_CASE. http://go/ipa/123', - path: ['components','schemas', 'SchemaName', 'properties', 'exampleProperty', 'enum'], + path: ['components', 'schemas', 'SchemaName', 'properties', 'exampleProperty', 'enum'], severity: DiagnosticSeverity.Warning, }, { code: 'xgen-IPA-123-enum-values-must-be-upper-snake-case', message: 'exampleB enum value must be UPPER_SNAKE_CASE. http://go/ipa/123', - path: ['components','schemas', 'SchemaName', 'properties', 'exampleProperty', 'enum'], + path: ['components', 'schemas', 'SchemaName', 'properties', 'exampleProperty', 'enum'], severity: DiagnosticSeverity.Warning, - } + }, ], }, { @@ -87,7 +87,7 @@ testRule('xgen-IPA-123-enum-values-must-be-upper-snake-case', [ }, ], }, - } + }, }, }, errors: [], @@ -102,7 +102,7 @@ testRule('xgen-IPA-123-enum-values-must-be-upper-snake-case', [ { schema: { 'x-xgen-IPA-exception': { - 'xgen-IPA-123-enum-values-must-be-upper-snake-case' : {} + 'xgen-IPA-123-enum-values-must-be-upper-snake-case': {}, }, type: 'string', enum: ['exampleA', 'exampleB'], @@ -110,22 +110,22 @@ testRule('xgen-IPA-123-enum-values-must-be-upper-snake-case', [ }, ], }, - } + }, }, }, errors: [ { code: 'xgen-IPA-123-enum-values-must-be-upper-snake-case', message: 'exampleA enum value must be UPPER_SNAKE_CASE. http://go/ipa/123', - path: ['paths','/a/{exampleId}', 'get', 'parameters', '0', 'schema', 'enum'], + path: ['paths', '/a/{exampleId}', 'get', 'parameters', '0', 'schema', 'enum'], severity: DiagnosticSeverity.Warning, }, { code: 'xgen-IPA-123-enum-values-must-be-upper-snake-case', message: 'exampleB enum value must be UPPER_SNAKE_CASE. http://go/ipa/123', - path: ['paths','/a/{exampleId}', 'get', 'parameters', '0', 'schema', 'enum'], + path: ['paths', '/a/{exampleId}', 'get', 'parameters', '0', 'schema', 'enum'], severity: DiagnosticSeverity.Warning, - } + }, ], }, { @@ -143,22 +143,22 @@ testRule('xgen-IPA-123-enum-values-must-be-upper-snake-case', [ }, ], }, - } + }, }, }, errors: [ { code: 'xgen-IPA-123-enum-values-must-be-upper-snake-case', message: 'exampleA enum value must be UPPER_SNAKE_CASE. http://go/ipa/123', - path: ['paths','/a/{exampleId}', 'get', 'parameters', '0', 'schema', 'enum'], + path: ['paths', '/a/{exampleId}', 'get', 'parameters', '0', 'schema', 'enum'], severity: DiagnosticSeverity.Warning, }, { code: 'xgen-IPA-123-enum-values-must-be-upper-snake-case', message: 'exampleB enum value must be UPPER_SNAKE_CASE. http://go/ipa/123', - path: ['paths','/a/{exampleId}', 'get', 'parameters', '0', 'schema', 'enum'], + path: ['paths', '/a/{exampleId}', 'get', 'parameters', '0', 'schema', 'enum'], severity: DiagnosticSeverity.Warning, - } + }, ], }, ]); diff --git a/tools/spectral/ipa/rulesets/functions/eachEnumValueMustBeUpperSnakeCase.js b/tools/spectral/ipa/rulesets/functions/eachEnumValueMustBeUpperSnakeCase.js index d64d920984..c7fcb4b748 100644 --- a/tools/spectral/ipa/rulesets/functions/eachEnumValueMustBeUpperSnakeCase.js +++ b/tools/spectral/ipa/rulesets/functions/eachEnumValueMustBeUpperSnakeCase.js @@ -13,11 +13,11 @@ function getSchemaPath(path) { } } -export default (input, _, {path}) => { +export default (input, _, { path }) => { //There are two path types for enum definition //First type: components.schemas.schemaName.*.enum //Second type: paths.*.method.parameters[*].schema.enum - if(hasException(getSchemaPath(path), RULE_NAME)) { + if (hasException(getSchemaPath(path), RULE_NAME)) { return; } From fb874f59005004859467549303ea3459b3ed3fb7 Mon Sep 17 00:00:00 2001 From: Yeliz Henden Date: Fri, 20 Dec 2024 12:48:03 +0000 Subject: [PATCH 6/8] CLOUDP-287249: IPA-123: Validate enums must be UPPER_SNAKE_CASE --- .../eachEnumValueMustBeUpperSnakeCase.test.js | 23 ++---- .../eachEnumValueMustBeUpperSnakeCase.js | 19 ++--- ...ternatesBetweenResourceNameAndPathParam.js | 2 +- .../functions/utils/componentUtils.js | 77 +++++++++++++++++++ .../ipa/rulesets/functions/utils/pathUtils.js | 14 ---- 5 files changed, 89 insertions(+), 46 deletions(-) create mode 100644 tools/spectral/ipa/rulesets/functions/utils/componentUtils.js delete mode 100644 tools/spectral/ipa/rulesets/functions/utils/pathUtils.js diff --git a/tools/spectral/ipa/__tests__/eachEnumValueMustBeUpperSnakeCase.test.js b/tools/spectral/ipa/__tests__/eachEnumValueMustBeUpperSnakeCase.test.js index 1b9edbb7e9..4fbfec980b 100644 --- a/tools/spectral/ipa/__tests__/eachEnumValueMustBeUpperSnakeCase.test.js +++ b/tools/spectral/ipa/__tests__/eachEnumValueMustBeUpperSnakeCase.test.js @@ -27,7 +27,7 @@ testRule('xgen-IPA-123-enum-values-must-be-upper-snake-case', [ schemas: { SchemaName: { 'x-xgen-IPA-exception': { - 'xgen-IPA-123-enum-values-must-be-upper-snake-case': {}, + 'xgen-IPA-123-enum-values-must-be-upper-snake-case': 'reason', }, properties: { exampleProperty: { @@ -42,7 +42,7 @@ testRule('xgen-IPA-123-enum-values-must-be-upper-snake-case', [ errors: [], }, { - name: 'invalid schema with exception - components.schemas', + name: 'invalid schema - components.schemas', document: { components: { schemas: { @@ -93,7 +93,7 @@ testRule('xgen-IPA-123-enum-values-must-be-upper-snake-case', [ errors: [], }, { - name: 'invalid schema with exceptions - paths.*', + name: 'invalid schema with exception - paths.*', document: { paths: { '/a/{exampleId}': { @@ -102,7 +102,7 @@ testRule('xgen-IPA-123-enum-values-must-be-upper-snake-case', [ { schema: { 'x-xgen-IPA-exception': { - 'xgen-IPA-123-enum-values-must-be-upper-snake-case': {}, + 'xgen-IPA-123-enum-values-must-be-upper-snake-case': 'reason', }, type: 'string', enum: ['exampleA', 'exampleB'], @@ -113,20 +113,7 @@ testRule('xgen-IPA-123-enum-values-must-be-upper-snake-case', [ }, }, }, - errors: [ - { - code: 'xgen-IPA-123-enum-values-must-be-upper-snake-case', - message: 'exampleA enum value must be UPPER_SNAKE_CASE. http://go/ipa/123', - path: ['paths', '/a/{exampleId}', 'get', 'parameters', '0', 'schema', 'enum'], - severity: DiagnosticSeverity.Warning, - }, - { - code: 'xgen-IPA-123-enum-values-must-be-upper-snake-case', - message: 'exampleB enum value must be UPPER_SNAKE_CASE. http://go/ipa/123', - path: ['paths', '/a/{exampleId}', 'get', 'parameters', '0', 'schema', 'enum'], - severity: DiagnosticSeverity.Warning, - }, - ], + errors: [], }, { name: 'invalid schema - paths.*', diff --git a/tools/spectral/ipa/rulesets/functions/eachEnumValueMustBeUpperSnakeCase.js b/tools/spectral/ipa/rulesets/functions/eachEnumValueMustBeUpperSnakeCase.js index c7fcb4b748..8ad1f23b2b 100644 --- a/tools/spectral/ipa/rulesets/functions/eachEnumValueMustBeUpperSnakeCase.js +++ b/tools/spectral/ipa/rulesets/functions/eachEnumValueMustBeUpperSnakeCase.js @@ -1,23 +1,16 @@ import { hasException } from './utils/exceptions.js'; +import {getSchemaPath, resolveObject} from './utils/componentUtils.js' import { casing } from '@stoplight/spectral-functions'; const RULE_NAME = 'xgen-IPA-123-enum-values-must-be-upper-snake-case'; const ERROR_MESSAGE = 'enum value must be UPPER_SNAKE_CASE.'; -function getSchemaPath(path) { - if (path.includes('components')) { - return path.slice(0, 3); - } else if (path.includes('paths')) { - const index = path.findIndex((item) => item === 'schema'); - return path.slice(0, index + 1); - } -} +export default (input, _, { path, documentInventory }) => { -export default (input, _, { path }) => { - //There are two path types for enum definition - //First type: components.schemas.schemaName.*.enum - //Second type: paths.*.method.parameters[*].schema.enum - if (hasException(getSchemaPath(path), RULE_NAME)) { + const oas = documentInventory.resolved; + const schemaPath = getSchemaPath(path); + const schemaObject = resolveObject(oas, schemaPath); + if (hasException(schemaObject, RULE_NAME)) { return; } diff --git a/tools/spectral/ipa/rulesets/functions/eachPathAlternatesBetweenResourceNameAndPathParam.js b/tools/spectral/ipa/rulesets/functions/eachPathAlternatesBetweenResourceNameAndPathParam.js index 234cb2c5a4..eb418aae61 100644 --- a/tools/spectral/ipa/rulesets/functions/eachPathAlternatesBetweenResourceNameAndPathParam.js +++ b/tools/spectral/ipa/rulesets/functions/eachPathAlternatesBetweenResourceNameAndPathParam.js @@ -1,4 +1,4 @@ -import { isPathParam } from './utils/pathUtils.js'; +import { isPathParam } from './utils/componentUtils.js'; import { hasException } from './utils/exceptions.js'; const RULE_NAME = 'xgen-IPA-102-path-alternate-resource-name-path-param'; diff --git a/tools/spectral/ipa/rulesets/functions/utils/componentUtils.js b/tools/spectral/ipa/rulesets/functions/utils/componentUtils.js new file mode 100644 index 0000000000..11d055d573 --- /dev/null +++ b/tools/spectral/ipa/rulesets/functions/utils/componentUtils.js @@ -0,0 +1,77 @@ +/** + * Checks if a string belongs to a path parameter or a path parameter with a custom method. + * + * A path parameter has the format: `{paramName}` + * A path parameter with a custom method has the format: `{paramName}:customMethod` + * + * @param {string} str - A string extracted from a path split by slashes. + * @returns {boolean} True if the string matches the expected formats, false otherwise. + */ +export function isPathParam(str) { + const pathParamRegEx = new RegExp(`^{[a-z][a-zA-Z0-9]*}$`); + const pathParamWithCustomMethodRegEx = new RegExp(`^{[a-z][a-zA-Z0-9]*}:[a-z][a-zA-Z0-9]*$`); + return pathParamRegEx.test(str) || pathParamWithCustomMethodRegEx.test(str); +} + +/** + * Extracts the schema path from the given JSONPath array. + * + * This function is designed to handle two types of paths commonly encountered in OpenAPI definitions: + * + * 1. **Component Schema Paths**: + * - Represented as: `components.schemas.schemaName.*.enum` + * - This path indicates that the enum is defined within a schema under `components.schemas`. + * - The function returns the first three elements (`["components", "schemas", "schemaName"]`). + * + * 2. **Parameter Schema Paths**: + * - Represented as: `paths.*.method.parameters[*].schema.enum` + * - This path indicates that the enum is part of a parameter's schema in an operation. + * - The function identifies the location of `schema` in the path and returns everything up to (and including) it. + * + * @param {string[]} path - An array representing the JSONPath structure of the OpenAPI definition. + * @returns {string[]} The truncated path pointing to the schema object. + */ +export function getSchemaPath(path) { + if (path.includes('components')) { + return path.slice(0, 3); + } else if (path.includes('paths')) { + const index = path.findIndex((item) => item === 'schema'); + return path.slice(0, index + 1); + } +} + +/** + * Resolves the value of a nested property within an OpenAPI structure using a given path. + * + * This function traverses an OpenAPI object based on a specified path (array of keys) + * and retrieves the value at the end of the path. If any key in the path is not found, + * or the value is undefined at any point, the function will return `undefined`. + * + * @param {Object} oas - The entire OpenAPI Specification object. + * This represents the root of the OpenAPI document. + * @param {string[]} objectPath - An array of strings representing the path to the desired value. + * Each element corresponds to a key in the OAS object hierarchy. + * For example, `['components', 'schemas', 'MySchema', 'properties']`. + * @returns {*} The value at the specified path within the OpenAPI object, or `undefined` if the path is invalid. + * + * @example + * const oas = { + * components: { + * schemas: { + * MySchema: { + * properties: { + * fieldName: { type: 'string' } + * } + * } + * } + * } + * }; + * + * const result = resolveObject(oas, ['components', 'schemas', 'MySchema', 'properties']); + * console.log(result); // Output: { fieldName: { type: 'string' } } + */ + export function resolveObject(oas, objectPath) { + return objectPath.reduce((current, key) => { + return current && current[key] ? current[key] : undefined; + }, oas); +} \ No newline at end of file diff --git a/tools/spectral/ipa/rulesets/functions/utils/pathUtils.js b/tools/spectral/ipa/rulesets/functions/utils/pathUtils.js deleted file mode 100644 index 34d1910175..0000000000 --- a/tools/spectral/ipa/rulesets/functions/utils/pathUtils.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Checks if a string belongs to a path parameter or a path parameter with a custom method. - * - * A path parameter has the format: `{paramName}` - * A path parameter with a custom method has the format: `{paramName}:customMethod` - * - * @param {string} str - A string extracted from a path split by slashes. - * @returns {boolean} True if the string matches the expected formats, false otherwise. - */ -export function isPathParam(str) { - const pathParamRegEx = new RegExp(`^{[a-z][a-zA-Z0-9]*}$`); - const pathParamWithCustomMethodRegEx = new RegExp(`^{[a-z][a-zA-Z0-9]*}:[a-z][a-zA-Z0-9]*$`); - return pathParamRegEx.test(str) || pathParamWithCustomMethodRegEx.test(str); -} From a5d137e2a60be4bf0ea5c641ac74a1ca3bbec70f Mon Sep 17 00:00:00 2001 From: Yeliz Henden Date: Fri, 20 Dec 2024 12:50:43 +0000 Subject: [PATCH 7/8] prettier fix --- .../rulesets/functions/eachEnumValueMustBeUpperSnakeCase.js | 3 +-- .../spectral/ipa/rulesets/functions/utils/componentUtils.js | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/tools/spectral/ipa/rulesets/functions/eachEnumValueMustBeUpperSnakeCase.js b/tools/spectral/ipa/rulesets/functions/eachEnumValueMustBeUpperSnakeCase.js index 8ad1f23b2b..e740b980b3 100644 --- a/tools/spectral/ipa/rulesets/functions/eachEnumValueMustBeUpperSnakeCase.js +++ b/tools/spectral/ipa/rulesets/functions/eachEnumValueMustBeUpperSnakeCase.js @@ -1,12 +1,11 @@ import { hasException } from './utils/exceptions.js'; -import {getSchemaPath, resolveObject} from './utils/componentUtils.js' +import { getSchemaPath, resolveObject } from './utils/componentUtils.js'; import { casing } from '@stoplight/spectral-functions'; const RULE_NAME = 'xgen-IPA-123-enum-values-must-be-upper-snake-case'; const ERROR_MESSAGE = 'enum value must be UPPER_SNAKE_CASE.'; export default (input, _, { path, documentInventory }) => { - const oas = documentInventory.resolved; const schemaPath = getSchemaPath(path); const schemaObject = resolveObject(oas, schemaPath); diff --git a/tools/spectral/ipa/rulesets/functions/utils/componentUtils.js b/tools/spectral/ipa/rulesets/functions/utils/componentUtils.js index 11d055d573..2eb2f86d9e 100644 --- a/tools/spectral/ipa/rulesets/functions/utils/componentUtils.js +++ b/tools/spectral/ipa/rulesets/functions/utils/componentUtils.js @@ -48,9 +48,7 @@ export function getSchemaPath(path) { * or the value is undefined at any point, the function will return `undefined`. * * @param {Object} oas - The entire OpenAPI Specification object. - * This represents the root of the OpenAPI document. * @param {string[]} objectPath - An array of strings representing the path to the desired value. - * Each element corresponds to a key in the OAS object hierarchy. * For example, `['components', 'schemas', 'MySchema', 'properties']`. * @returns {*} The value at the specified path within the OpenAPI object, or `undefined` if the path is invalid. * @@ -70,8 +68,8 @@ export function getSchemaPath(path) { * const result = resolveObject(oas, ['components', 'schemas', 'MySchema', 'properties']); * console.log(result); // Output: { fieldName: { type: 'string' } } */ - export function resolveObject(oas, objectPath) { +export function resolveObject(oas, objectPath) { return objectPath.reduce((current, key) => { return current && current[key] ? current[key] : undefined; }, oas); -} \ No newline at end of file +} From 35c3f29143c5b2c62ca04c59aa3ffaf9fdc953d1 Mon Sep 17 00:00:00 2001 From: Yeliz Henden Date: Fri, 20 Dec 2024 13:11:27 +0000 Subject: [PATCH 8/8] enum index fix --- .../__tests__/eachEnumValueMustBeUpperSnakeCase.test.js | 8 ++++---- .../functions/eachEnumValueMustBeUpperSnakeCase.js | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tools/spectral/ipa/__tests__/eachEnumValueMustBeUpperSnakeCase.test.js b/tools/spectral/ipa/__tests__/eachEnumValueMustBeUpperSnakeCase.test.js index 4fbfec980b..14854718a2 100644 --- a/tools/spectral/ipa/__tests__/eachEnumValueMustBeUpperSnakeCase.test.js +++ b/tools/spectral/ipa/__tests__/eachEnumValueMustBeUpperSnakeCase.test.js @@ -61,13 +61,13 @@ testRule('xgen-IPA-123-enum-values-must-be-upper-snake-case', [ { code: 'xgen-IPA-123-enum-values-must-be-upper-snake-case', message: 'exampleA enum value must be UPPER_SNAKE_CASE. http://go/ipa/123', - path: ['components', 'schemas', 'SchemaName', 'properties', 'exampleProperty', 'enum'], + path: ['components', 'schemas', 'SchemaName', 'properties', 'exampleProperty', 'enum', '0'], severity: DiagnosticSeverity.Warning, }, { code: 'xgen-IPA-123-enum-values-must-be-upper-snake-case', message: 'exampleB enum value must be UPPER_SNAKE_CASE. http://go/ipa/123', - path: ['components', 'schemas', 'SchemaName', 'properties', 'exampleProperty', 'enum'], + path: ['components', 'schemas', 'SchemaName', 'properties', 'exampleProperty', 'enum', '1'], severity: DiagnosticSeverity.Warning, }, ], @@ -137,13 +137,13 @@ testRule('xgen-IPA-123-enum-values-must-be-upper-snake-case', [ { code: 'xgen-IPA-123-enum-values-must-be-upper-snake-case', message: 'exampleA enum value must be UPPER_SNAKE_CASE. http://go/ipa/123', - path: ['paths', '/a/{exampleId}', 'get', 'parameters', '0', 'schema', 'enum'], + path: ['paths', '/a/{exampleId}', 'get', 'parameters', '0', 'schema', 'enum', '0'], severity: DiagnosticSeverity.Warning, }, { code: 'xgen-IPA-123-enum-values-must-be-upper-snake-case', message: 'exampleB enum value must be UPPER_SNAKE_CASE. http://go/ipa/123', - path: ['paths', '/a/{exampleId}', 'get', 'parameters', '0', 'schema', 'enum'], + path: ['paths', '/a/{exampleId}', 'get', 'parameters', '0', 'schema', 'enum', '1'], severity: DiagnosticSeverity.Warning, }, ], diff --git a/tools/spectral/ipa/rulesets/functions/eachEnumValueMustBeUpperSnakeCase.js b/tools/spectral/ipa/rulesets/functions/eachEnumValueMustBeUpperSnakeCase.js index e740b980b3..1850a44ca2 100644 --- a/tools/spectral/ipa/rulesets/functions/eachEnumValueMustBeUpperSnakeCase.js +++ b/tools/spectral/ipa/rulesets/functions/eachEnumValueMustBeUpperSnakeCase.js @@ -14,16 +14,16 @@ export default (input, _, { path, documentInventory }) => { } const errors = []; - for (const enumValue of input) { + input.forEach((enumValue, index) => { const isUpperSnakeCase = casing(enumValue, { type: 'macro' }); if (isUpperSnakeCase) { errors.push({ + path: [...path, index], message: `${enumValue} ${ERROR_MESSAGE} `, - path: path.concat(enumValue), }); } - } + }); return errors; };