Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion babel.config.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"presets": ["@babel/preset-env"],
"plugins": [
"babel-plugin-transform-import-meta"]
"babel-plugin-transform-import-meta",
"@babel/plugin-transform-modules-commonjs"]
}
958 changes: 760 additions & 198 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
"precommit": "husky install"
},
"jest": {
"transformIgnorePatterns": [
"/node_modules/(?!ember-inflector/)"
],
"transform": {
"^.+\\.[t|j]sx?$": "babel-jest"
},
Expand All @@ -31,17 +34,20 @@
"@stoplight/spectral-ruleset-bundler": "^1.6.3",
"apache-arrow": "^20.0.0",
"dotenv": "^17.0.1",
"ember-inflector": "^6.0.0",
"eslint-plugin-jest": "^29.0.1",
"markdown-table": "^3.0.4",
"openapi-to-postmanv2": "5.0.0",
"parquet-wasm": "^0.6.1"
},
"devDependencies": {
"@babel/preset-env": "^7.27.2",
"@babel/core": "^7.28.0",
"@babel/preset-env": "^7.28.0",
"@eslint/js": "^9.28.0",
"@jest/globals": "^30.0.2",
"@stoplight/types": "^14.1.1",
"aws-sdk-client-mock": "^4.1.0",
"babel-jest": "^30.0.2",
"babel-plugin-transform-import-meta": "^2.3.3",
"eslint": "^9.30.1",
"eslint-plugin-require-extensions": "^0.1.3",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { describe, expect, it } from '@jest/globals';
import { generateOperationID } from '../../rulesets/functions/utils/operationIdGeneration';

describe('tools/spectral/ipa/utils/operationIdGeneration.js', () => {
it('should singularize all nouns', () => {
expect(generateOperationID('create', '/groups/{groupId}/clusters')).toEqual('createGroupCluster');
expect(generateOperationID('delete', '/groups/{groupId}/clusters/{clusterName}')).toEqual('deleteGroupCluster');
expect(generateOperationID('get', '/groups/{groupId}/clusters/{clusterName}')).toEqual('getGroupCluster');
expect(generateOperationID('update', '/groups/{groupId}/clusters/{clusterName}')).toEqual('updateGroupCluster');
expect(generateOperationID('pause', '/groups/{groupId}/clusters/{clusterName}')).toEqual('pauseGroupCluster');
});

it('should leave the final noun as is', () => {
expect(generateOperationID('list', '/groups/{groupId}/clusters')).toEqual('listGroupClusters');
expect(generateOperationID('get', '/groups/{groupId}/settings')).toEqual('getGroupSettings');
expect(generateOperationID('update', '/groups/{groupId}/settings')).toEqual('updateGroupSettings');
expect(generateOperationID('search', '/groups/{groupId}/clusters')).toEqual('searchGroupClusters');
expect(
generateOperationID(
'get',
'/groups/{groupId}/clusters/{clusterName}/{clusterView}/{databaseName}/{collectionName}/collStats/measurements'
)
).toEqual('getGroupClusterCollStatMeasurements');
expect(generateOperationID('grant', '/api/atlas/v2/groups/{groupId}/access')).toEqual('grantGroupAccess');
});

it('should split camelCase method names', () => {
expect(generateOperationID('addNode', '/groups/{groupId}/clusters/{clusterName}')).toEqual('addGroupClusterNode');
});

it('should accomodate legacy custom methods', () => {
expect(generateOperationID('', '/api/atlas/v2/groups/{groupId}/clusters/{clusterName}/restartPrimaries')).toEqual(
'restartGroupClusterPrimaries'
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { singularize } from 'ember-inflector';
import { isPathParam, removePrefix, isSingleResourceIdentifier } from './resourceEvaluation.js';
import { casing } from '@stoplight/spectral-functions';

/**
* Returns IPA Compliant Operation ID.
*
* @param method the standard method name (create, update, get etc.), custom method name, or empty string (only for legacy custom methods)
* @param path the path for the endpoint
*/
export function generateOperationID(method, path) {
let resourceIdentifier = removePrefix(path);
let nouns = resourceIdentifier
.split('/')
.filter((id) => id.length > 0 && !isPathParam(id))
.map((noun) => capitalize(noun));

// legacy custom method - use end of path as custom method name
if (method === '') {
method = path.split('/').pop();
nouns.pop();
}

let verb = deriveActionVerb(method);

// if custom method name is multiple words, add trailing nouns to the operation ID
if (!casing(method, { type: 'camel' }) && method.length > verb.length) {
nouns.push(method.slice(verb.length));
}

let opID = verb;
for (let i = 0; i < nouns.length - 1; i++) {
opID += singularize(nouns[i]);
}

// singularize final noun, dependent on resource identifier
if (isSingleResourceIdentifier(resourceIdentifier) || verb == 'create') {
nouns[nouns.length - 1] = singularize(nouns[nouns.length - 1]);
}

opID += nouns.pop();

return opID;
}

/**
* Derives action verb from custom method name. Returns standard method names as is.
*
* @param method the custom method name
*/
function deriveActionVerb(method) {
// custom method name is camelCase return first word (assumed verb)
if (!casing(method, { type: 'camel' })) {
return method.match(/[A-Z]?[a-z]+/g)[0];
}

return method;
}

function capitalize(val) {
return String(val).charAt(0).toUpperCase() + String(val).slice(1);
}
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ function resourceBelongsToSingleParent(resourcePath) {
}

// TODO move prefixes to be rule arguments
function removePrefix(path) {
export function removePrefix(path) {
if (path.startsWith(AUTH_PREFIX)) {
return path.slice(AUTH_PREFIX.length);
}
Expand Down
Loading