diff --git a/tools/spectral/ipa/__tests__/metrics/metricCollectionUtils.test.js b/tools/spectral/ipa/__tests__/metrics/metricCollectionUtils.test.js new file mode 100644 index 0000000000..283902b0c2 --- /dev/null +++ b/tools/spectral/ipa/__tests__/metrics/metricCollectionUtils.test.js @@ -0,0 +1,60 @@ +import { describe, expect, it } from '@jest/globals'; +import { extractTeamOwnership, getSeverityPerRule } from '../../metrics/utils/metricCollectionUtils.js'; + +describe('tools/spectral/ipa/metrics/utils/metricCollectionUtils.js', () => { + describe('getSeverityPerRule', () => { + const testRuleSet = { + rules: { + rule1: { + definition: { + severity: 1, + }, + }, + rule2: { + definition: { + severity: 2, + }, + }, + }, + }; + it('maps the rule severities correctly', () => { + expect(getSeverityPerRule(testRuleSet)).toEqual({ + rule1: 1, + rule2: 2, + }); + }); + }); + + describe('extractTeamOwnership', () => { + const testOas = { + paths: { + '/resource1': { + get: { + description: 'get resource', + 'x-xgen-owner-team': 'team1', + }, + post: { + description: 'create resource', + 'x-xgen-owner-team': 'team1', + }, + }, + '/resource2': { + get: { + description: 'get resource', + 'x-xgen-owner-team': 'team2', + }, + post: { + description: 'create resource', + 'x-xgen-owner-team': 'team2', + }, + }, + }, + }; + it('maps the path ownership correctly', () => { + expect(extractTeamOwnership(testOas)).toEqual({ + '/resource1': 'team1', + '/resource2': 'team2', + }); + }); + }); +}); diff --git a/tools/spectral/ipa/__tests__/utils/componentUtils.test.js b/tools/spectral/ipa/__tests__/utils/componentUtils.test.js new file mode 100644 index 0000000000..fd04c5269f --- /dev/null +++ b/tools/spectral/ipa/__tests__/utils/componentUtils.test.js @@ -0,0 +1,149 @@ +import { describe, expect, it } from '@jest/globals'; +import { + isPathParam, + pathIsForRequestVersion, + pathIsForResponseVersion, + resolveObject, +} from '../../rulesets/functions/utils/componentUtils.js'; + +describe('tools/spectral/ipa/rulesets/functions/utils/componentUtils.js', () => { + describe('isPathParam', () => { + it('returns true if the string is a path param', () => { + expect(isPathParam('{id}')).toEqual(true); + }); + it('returns true if the string is a path param with a custom method', () => { + expect(isPathParam('{id}:custom')).toEqual(true); + }); + it('returns false if the string is not a path param', () => { + expect(isPathParam('resource')).toEqual(false); + expect(isPathParam('resource:custom')).toEqual(false); + }); + }); + + describe('resolveObject', () => { + const testOas = { + paths: { + '/resource': { + get: { + description: 'get resource', + }, + }, + }, + components: { + schemas: { + MySchema: { + properties: { + fieldName: { type: 'string' }, + }, + }, + }, + }, + }; + + it('resolves the OAS component based on the path', () => { + expect(resolveObject(testOas, ['components', 'schemas', 'MySchema'])).toEqual( + testOas.components.schemas.MySchema + ); + expect(resolveObject(testOas, ['components', 'schemas', 'MySchema', 'properties', 'fieldName'])).toEqual( + testOas.components.schemas.MySchema.properties.fieldName + ); + expect(resolveObject(testOas, ['paths', '/resource', 'get'])).toEqual(testOas.paths['/resource'].get); + }); + it('returns undefined if the OAS does not contain the component based on the path', () => { + expect(resolveObject(testOas, ['components', 'schemas', 'MySchema2'])).toEqual(undefined); + expect(resolveObject(testOas, ['components', 'schemas', 'MySchema', 'properties', 'fieldName2'])).toEqual( + undefined + ); + expect(resolveObject(testOas, ['paths', '/resource/{id}', 'get'])).toEqual(undefined); + }); + }); + + describe('pathIsForResponseVersion', () => { + it('returns true for a path for a response version', () => { + expect( + pathIsForResponseVersion([ + 'paths', + '/resource/{id}', + 'get', + 'responses', + '200', + 'content', + 'application/vnd.atlas.2023-08-05+json', + ]) + ).toEqual(true); + }); + it('returns true for a path for a schema in a response version', () => { + expect( + pathIsForResponseVersion([ + 'paths', + '/resource/{id}', + 'get', + 'responses', + '200', + 'content', + 'application/vnd.atlas.2023-08-05+json', + 'schema', + ]) + ).toEqual(true); + }); + it('returns false for a path for a schema in a request version', () => { + expect( + pathIsForResponseVersion([ + 'paths', + '/resource/{id}', + 'get', + 'requestBody', + 'content', + 'application/vnd.atlas.2023-08-05+json', + ]) + ).toEqual(false); + }); + it('returns false for a path for a schema in components', () => { + expect(pathIsForResponseVersion(['components', 'schemas', 'ExampleSchema'])).toEqual(false); + }); + }); + + describe('pathIsForRequestVersion', () => { + it('returns true for a request version', () => { + expect( + pathIsForRequestVersion([ + 'paths', + '/resource/{id}', + 'get', + 'requestBody', + 'content', + 'application/vnd.atlas.2023-08-05+json', + ]) + ).toEqual(true); + }); + it('returns true for a path for a schema in a request version', () => { + expect( + pathIsForRequestVersion([ + 'paths', + '/resource/{id}', + 'get', + 'requestBody', + 'content', + 'application/vnd.atlas.2023-08-05+json', + 'schema', + ]) + ).toEqual(true); + }); + it('returns false for a path for a response version', () => { + expect( + pathIsForRequestVersion([ + 'paths', + '/resource/{id}', + 'get', + 'responses', + '200', + 'content', + 'application/vnd.atlas.2023-08-05+json', + ]) + ).toEqual(false); + }); + it('returns false for a path for a schema in components', () => { + expect(pathIsForRequestVersion(['components', 'schemas', 'ExampleSchema'])).toEqual(false); + }); + }); +}); diff --git a/tools/spectral/ipa/__tests__/utils/extensions.test.js b/tools/spectral/ipa/__tests__/utils/extensions.test.js index 6088ec499e..4bc9d5c423 100644 --- a/tools/spectral/ipa/__tests__/utils/extensions.test.js +++ b/tools/spectral/ipa/__tests__/utils/extensions.test.js @@ -4,32 +4,23 @@ import { hasMethodVerbOverride, hasOperationIdOverride, getOperationIdOverride, + hasVerbOverride, } from '../../rulesets/functions/utils/extensions'; -const methodWithExtension = { +const operationWithVerbOverride = { 'x-xgen-method-verb-override': { verb: 'get', customMethod: false, }, }; -const customMethod = { +const operationWithCustomMethodVerbOverride = { 'x-xgen-method-verb-override': { verb: 'add', customMethod: true, }, }; -const endpointWithMethodExtension = { - delete: { - 'x-xgen-method-verb-override': { verb: 'remove', customMethod: true }, - }, -}; - -const endpointWithNoMethodExtension = { - exception: true, -}; - const operationWithOperationIdOverride = { operationId: 'operationId', 'x-xgen-operation-id-override': 'customOperationId', @@ -40,32 +31,32 @@ const operationWithEmptyOperationIdOverride = { 'x-xgen-operation-id-override': '', }; -const operationWithNoOperationIdOverride = { +const operationWithNoOverrides = { operationId: 'operationId', }; describe('tools/spectral/ipa/rulesets/functions/utils/extensions.js', () => { describe('hasCustomMethodOverride', () => { it('returns true if the method has the extension with the customMethod boolean set to true', () => { - expect(hasCustomMethodOverride(customMethod)).toBe(true); + expect(hasCustomMethodOverride(operationWithCustomMethodVerbOverride)).toBe(true); }); it('returns false if the method does not have the extension', () => { expect(hasCustomMethodOverride({})).toBe(false); }); it('returns false if the method has the extension but is not a custom method', () => { - expect(hasCustomMethodOverride(methodWithExtension)).toBe(false); + expect(hasCustomMethodOverride(operationWithVerbOverride)).toBe(false); }); }); describe('hasMethodVerbOverride', () => { it('returns true if the method has the extension with the expected verb', () => { - expect(hasMethodVerbOverride(methodWithExtension, 'get')).toBe(true); + expect(hasMethodVerbOverride(operationWithVerbOverride, 'get')).toBe(true); }); it('returns false if the method does not have the extension', () => { expect(hasMethodVerbOverride({}, 'get')).toBe(false); }); it('returns false if the method has the extension but with an unexpected verb', () => { - expect(hasMethodVerbOverride(methodWithExtension, 'put')).toBe(false); + expect(hasMethodVerbOverride(operationWithVerbOverride, 'put')).toBe(false); }); }); @@ -77,7 +68,7 @@ describe('tools/spectral/ipa/rulesets/functions/utils/extensions.js', () => { expect(hasOperationIdOverride(operationWithEmptyOperationIdOverride)).toBe(true); }); it('returns false if the method does not have the extension', () => { - expect(hasOperationIdOverride(operationWithNoOperationIdOverride)).toBe(false); + expect(hasOperationIdOverride(operationWithNoOverrides)).toBe(false); }); }); @@ -89,7 +80,17 @@ describe('tools/spectral/ipa/rulesets/functions/utils/extensions.js', () => { expect(getOperationIdOverride(operationWithEmptyOperationIdOverride)).toBe(''); }); it('returns undefined if the method does not have the extension', () => { - expect(getOperationIdOverride(operationWithNoOperationIdOverride)).toBe(undefined); + expect(getOperationIdOverride(operationWithNoOverrides)).toBe(undefined); + }); + }); + + describe('hasVerbOverride', () => { + it('returns true if the method has the extension', () => { + expect(hasVerbOverride(operationWithVerbOverride)).toBe(true); + expect(hasVerbOverride(operationWithCustomMethodVerbOverride)).toBe(true); + }); + it('returns false if the method does not have the extension', () => { + expect(hasVerbOverride(operationWithNoOverrides)).toBe(false); }); }); }); diff --git a/tools/spectral/ipa/__tests__/utils/methodLogic.test.js b/tools/spectral/ipa/__tests__/utils/methodLogic.test.js new file mode 100644 index 0000000000..2bfefe251d --- /dev/null +++ b/tools/spectral/ipa/__tests__/utils/methodLogic.test.js @@ -0,0 +1,103 @@ +import { describe, expect, it } from '@jest/globals'; +import { isInvalidGetMethod, isInvalidListMethod } from '../../rulesets/functions/utils/methodLogic.js'; + +const standardResourcePathItems = { + '/resource': { + get: {}, + post: {}, + }, + '/resource/{id}': { + get: {}, + patch: {}, + delete: {}, + }, +}; + +const standardResourceWithCustomMethodPathItems = { + standardResourcePathItems, + '/resource/{id}:custom': { + get: {}, + }, +}; + +const singletonResourcePathItems = { + '/resource/{id}/singleton': { + get: {}, + patch: {}, + }, +}; +describe('tools/spectral/ipa/rulesets/functions/utils/methodLogic.js', () => { + describe('isInvalidGetMethod', () => { + const testCases = [ + { + description: 'resource collection identifier for standard resource', + resourcePaths: standardResourcePathItems, + methodPath: '/resource', + expectedIsInvalidGetMethod: true, + }, + { + description: 'custom method identifier for standard resource', + methodPath: '/resource/{id}:custom', + resourcePaths: standardResourceWithCustomMethodPathItems, + expectedIsInvalidGetMethod: true, + }, + { + description: 'single resource identifier for standard resource', + resourcePaths: standardResourcePathItems, + methodPath: '/resource/{id}', + expectedIsInvalidGetMethod: false, + }, + { + description: 'singleton resource identifier for singleton resource', + methodPath: '/resource/{id}/singleton', + resourcePaths: singletonResourcePathItems, + expectedIsInvalidGetMethod: false, + }, + ]; + + testCases.forEach((testCase) => { + it(`returns ${testCase.expectedIsInvalidGetMethod} for ${testCase.description}`, () => { + expect(isInvalidGetMethod(testCase.methodPath, testCase.resourcePaths)).toEqual( + testCase.expectedIsInvalidGetMethod + ); + }); + }); + }); + + describe('isInvalidListMethod', () => { + const testCases = [ + { + description: 'single resource identifier for standard resource', + resourcePaths: standardResourcePathItems, + methodPath: '/resource/{id}', + expectedIsInvalidGetMethod: true, + }, + { + description: 'singleton resource identifier for singleton resource', + methodPath: '/resource/{id}/singleton', + resourcePaths: singletonResourcePathItems, + expectedIsInvalidGetMethod: true, + }, + { + description: 'custom method identifier for standard resource', + methodPath: '/resource/{id}:custom', + resourcePaths: standardResourceWithCustomMethodPathItems, + expectedIsInvalidGetMethod: true, + }, + { + description: 'resource collection identifier for standard resource', + resourcePaths: standardResourcePathItems, + methodPath: '/resource', + expectedIsInvalidGetMethod: false, + }, + ]; + + testCases.forEach((testCase) => { + it(`returns ${testCase.expectedIsInvalidGetMethod} for ${testCase.description}`, () => { + expect(isInvalidListMethod(testCase.methodPath, testCase.resourcePaths)).toEqual( + testCase.expectedIsInvalidGetMethod + ); + }); + }); + }); +}); diff --git a/tools/spectral/ipa/__tests__/utils/methodUtils.test.js b/tools/spectral/ipa/__tests__/utils/methodUtils.test.js index 13da0b1577..59fcf70307 100644 --- a/tools/spectral/ipa/__tests__/utils/methodUtils.test.js +++ b/tools/spectral/ipa/__tests__/utils/methodUtils.test.js @@ -3,6 +3,8 @@ import { getAllSuccessfulResponseSchemas, getResponseOfGetMethodByMediaType, getResponseOfListMethodByMediaType, + getSchemaNameFromRef, + getSchemaRef, } from '../../rulesets/functions/utils/methodUtils.js'; describe('tools/spectral/ipa/rulesets/functions/utils/methodUtils.js', () => { @@ -182,4 +184,37 @@ describe('tools/spectral/ipa/rulesets/functions/utils/methodUtils.js', () => { }); }); }); + + describe('getSchemaRef', () => { + it('returns the ref value for a schema with $ref', () => { + expect( + getSchemaRef({ + $ref: '#/components/schemas/ExampleSchema', + }) + ).toEqual('#/components/schemas/ExampleSchema'); + }); + it('returns the ref value for a schema with items of $ref', () => { + expect( + getSchemaRef({ + type: 'array', + items: { + $ref: '#/components/schemas/ExampleItemsSchema', + }, + }) + ).toEqual('#/components/schemas/ExampleItemsSchema'); + }); + it('returns undefined for a schema with no $ref', () => { + expect( + getSchemaRef({ + type: 'string', + }) + ).toEqual(undefined); + }); + }); + + describe('getSchemaNameFromRef', () => { + it('returns the schema name from a schema $ref', () => { + expect(getSchemaNameFromRef('#/components/schemas/ExampleSchema')).toEqual('ExampleSchema'); + }); + }); }); diff --git a/tools/spectral/ipa/__tests__/utils/schemaUtils.test.js b/tools/spectral/ipa/__tests__/utils/schemaUtils.test.js index 8bad523f67..ac3229ea37 100644 --- a/tools/spectral/ipa/__tests__/utils/schemaUtils.test.js +++ b/tools/spectral/ipa/__tests__/utils/schemaUtils.test.js @@ -1,30 +1,137 @@ -import { splitCamelCase } from '../../rulesets/functions/utils/schemaUtils.js'; +import { + getSchemaPathFromEnumPath, + schemaIsArray, + schemaIsObject, + schemaIsPaginated, + splitCamelCase, +} from '../../rulesets/functions/utils/schemaUtils.js'; import { describe, expect, it } from '@jest/globals'; -describe('splitCamelCase', () => { - it('should split basic camelCase strings', () => { - expect(splitCamelCase('camelCase')).toEqual(['camel', 'case']); - expect(splitCamelCase('thisIsCamelCase')).toEqual(['this', 'is', 'camel', 'case']); +describe('tools/spectral/ipa/rulesets/functions/utils/schemaUtils.js', () => { + describe('splitCamelCase', () => { + it('should split basic camelCase strings', () => { + expect(splitCamelCase('camelCase')).toEqual(['camel', 'case']); + expect(splitCamelCase('thisIsCamelCase')).toEqual(['this', 'is', 'camel', 'case']); + }); + + it('should handle single-word strings', () => { + expect(splitCamelCase('word')).toEqual(['word']); + expect(splitCamelCase('Word')).toEqual(['word']); + }); + + it('should handle strings with numbers', () => { + expect(splitCamelCase('user123Name')).toEqual(['user123', 'name']); + expect(splitCamelCase('user123name')).toEqual(['user123name']); + }); + + it('should handle empty strings', () => { + expect(splitCamelCase('')).toEqual(['']); + }); + + it('should handle edge cases from the project', () => { + expect(splitCamelCase('project')).toEqual(['project']); + expect(splitCamelCase('projectId')).toEqual(['project', 'id']); + expect(splitCamelCase('myProjectDetails')).toEqual(['my', 'project', 'details']); + expect(splitCamelCase('projection')).toEqual(['projection']); + }); }); - it('should handle single-word strings', () => { - expect(splitCamelCase('word')).toEqual(['word']); - expect(splitCamelCase('Word')).toEqual(['word']); + describe('schemaIsPaginated', () => { + it('returns true for a schema with an array property named "results"', () => { + expect( + schemaIsPaginated({ + type: 'object', + properties: { + results: { + type: 'array', + }, + }, + }) + ).toEqual(true); + }); + it('returns false for a schema with a non-array property named "results"', () => { + expect( + schemaIsPaginated({ + type: 'object', + properties: { + results: { + type: 'string', + }, + }, + }) + ).toEqual(false); + }); + it('returns false for a schema with no property named "results"', () => { + expect( + schemaIsPaginated({ + type: 'object', + properties: { + list: { + type: 'array', + }, + }, + }) + ).toEqual(false); + }); + it('returns false for a schema with no properties', () => { + expect( + schemaIsPaginated({ + type: 'string', + }) + ).toEqual(false); + }); }); - it('should handle strings with numbers', () => { - expect(splitCamelCase('user123Name')).toEqual(['user123', 'name']); - expect(splitCamelCase('user123name')).toEqual(['user123name']); + describe('schemaIsArray', () => { + it('returns true for a schema with type array', () => { + expect( + schemaIsArray({ + type: 'array', + }) + ).toEqual(true); + }); + it('returns false for a schema with a non-array type', () => { + expect( + schemaIsArray({ + type: 'object', + }) + ).toEqual(false); + }); + it('returns false for a schema with no type', () => { + expect(schemaIsArray({})).toEqual(false); + }); }); - it('should handle empty strings', () => { - expect(splitCamelCase('')).toEqual(['']); + describe('schemaIsObject', () => { + it('returns true for a schema with type object', () => { + expect( + schemaIsObject({ + type: 'object', + }) + ).toEqual(true); + }); + it('returns false for a schema with a non-object type', () => { + expect( + schemaIsObject({ + type: 'array', + }) + ).toEqual(false); + }); + it('returns false for a schema with no type', () => { + expect(schemaIsObject({})).toEqual(false); + }); }); - it('should handle edge cases from the project', () => { - expect(splitCamelCase('project')).toEqual(['project']); - expect(splitCamelCase('projectId')).toEqual(['project', 'id']); - expect(splitCamelCase('myProjectDetails')).toEqual(['my', 'project', 'details']); - expect(splitCamelCase('projection')).toEqual(['projection']); + describe('getSchemaPathFromEnumPath', () => { + it('returns the expected path to the property in an enum schema', () => { + expect( + getSchemaPathFromEnumPath(['components', 'schemas', 'EnumSchema', 'properties', 'test', 'enum', 0]) + ).toEqual(['components', 'schemas', 'EnumSchema', 'properties', 'test']); + }); + it('returns the expected path to the property in an enum array schema', () => { + expect( + getSchemaPathFromEnumPath(['components', 'schemas', 'EnumSchema', 'properties', 'test', 'items', 'enum', 0]) + ).toEqual(['components', 'schemas', 'EnumSchema', 'properties', 'test']); + }); }); }); diff --git a/tools/spectral/ipa/rulesets/functions/IPA104ValidOperationID.js b/tools/spectral/ipa/rulesets/functions/IPA104ValidOperationID.js index cb5cce8140..962f718e6e 100644 --- a/tools/spectral/ipa/rulesets/functions/IPA104ValidOperationID.js +++ b/tools/spectral/ipa/rulesets/functions/IPA104ValidOperationID.js @@ -5,7 +5,7 @@ import { handleInternalError, } from './utils/collectionUtils.js'; import { hasException } from './utils/exceptions.js'; -import { getResourcePathItems, isCustomMethodIdentifier } from './utils/resourceEvaluation.js'; +import { getResourcePathItems } from './utils/resourceEvaluation.js'; import { hasCustomMethodOverride, hasMethodVerbOverride, VERB_OVERRIDE_EXTENSION } from './utils/extensions.js'; import { isInvalidGetMethod } from './utils/methodLogic.js'; import { validateOperationIdAndReturnErrors } from './utils/validations/validateOperationIdAndReturnErrors.js'; @@ -19,7 +19,6 @@ export default (input, { methodName }, { path, documentInventory }) => { if ( hasCustomMethodOverride(input) || - isCustomMethodIdentifier(resourcePath) || hasMethodVerbOverride(input, 'list') || (isInvalidGetMethod(resourcePath, resourcePaths) && !hasMethodVerbOverride(input, methodName)) ) { diff --git a/tools/spectral/ipa/rulesets/functions/IPA105ValidOperationID.js b/tools/spectral/ipa/rulesets/functions/IPA105ValidOperationID.js index 96ff085c3b..72080851de 100644 --- a/tools/spectral/ipa/rulesets/functions/IPA105ValidOperationID.js +++ b/tools/spectral/ipa/rulesets/functions/IPA105ValidOperationID.js @@ -5,7 +5,7 @@ import { collectException, handleInternalError, } from './utils/collectionUtils.js'; -import { getResourcePathItems, isCustomMethodIdentifier } from './utils/resourceEvaluation.js'; +import { getResourcePathItems } from './utils/resourceEvaluation.js'; import { isInvalidListMethod } from './utils/methodLogic.js'; import { hasCustomMethodOverride, hasMethodVerbOverride, VERB_OVERRIDE_EXTENSION } from './utils/extensions.js'; import { validateOperationIdAndReturnErrors } from './utils/validations/validateOperationIdAndReturnErrors.js'; @@ -19,7 +19,6 @@ export default (input, { methodName }, { path, documentInventory }) => { if ( hasCustomMethodOverride(input) || - isCustomMethodIdentifier(resourcePath) || hasMethodVerbOverride(input, 'get') || (isInvalidListMethod(resourcePath, resourcePaths) && !hasMethodVerbOverride(input, methodName)) ) { diff --git a/tools/spectral/ipa/rulesets/functions/utils/methodLogic.js b/tools/spectral/ipa/rulesets/functions/utils/methodLogic.js index 3677186895..b25d8fbc89 100644 --- a/tools/spectral/ipa/rulesets/functions/utils/methodLogic.js +++ b/tools/spectral/ipa/rulesets/functions/utils/methodLogic.js @@ -1,16 +1,16 @@ -import { isResourceCollectionIdentifier, isSingletonResource } from './resourceEvaluation.js'; +import { isCustomMethodIdentifier, isResourceCollectionIdentifier, isSingletonResource } from './resourceEvaluation.js'; /** * Checks whether the get method at a given path is valid * * @param resourcePath the resource path to inspect - * @param resourcePaths the resource paths generated by getResourcePathItems + * @param resourcePathItems the resource paths generated by getResourcePathItems * @returns true if the resourcePath has an invalid get method, false otherwise */ -export function isInvalidGetMethod(resourcePath, resourcePaths) { +export function isInvalidGetMethod(resourcePath, resourcePathItems) { return ( !lastIdentifierIsPathParam(resourcePath) && - !(isResourceCollectionIdentifier(resourcePath) && isSingletonResource(resourcePaths)) + !(isResourceCollectionIdentifier(resourcePath) && isSingletonResource(resourcePathItems)) ); } @@ -22,7 +22,11 @@ export function isInvalidGetMethod(resourcePath, resourcePaths) { * @returns true if the resourcePath has an invalid list method, false otherwise */ export function isInvalidListMethod(resourcePath, resourcePaths) { - return lastIdentifierIsPathParam(resourcePath) || isSingletonResource(resourcePaths); + return ( + lastIdentifierIsPathParam(resourcePath) || + isSingletonResource(resourcePaths) || + isCustomMethodIdentifier(resourcePath) + ); } function lastIdentifierIsPathParam(resourceIdentifier) {