Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
8 changes: 8 additions & 0 deletions .github/workflows/code-health-tools.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,14 @@ jobs:
- name: Run ESLint on JS files
run: |
npm run lint-js
- name: Check IPA docs up-to-date
run: |
npm run gen-ipa-docs
if [[ -n $(git status --porcelain) ]]; then
echo "IPA docs not up to date, please run 'npm run gen-ipa-docs' and commit the changes"
exit 1
fi
exit 0
- name: Install Go
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34
with:
Expand Down
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"format": "npx prettier . --write",
"format-check": "npx prettier . --check",
"lint-js": "npx eslint **/*.js",
"gen-ipa-docs": "node tools/spectral/ipa/scripts/generateRulesetReadme.js",
"ipa-validation": "spectral lint ./openapi/v2.yaml --ruleset=./tools/spectral/ipa/ipa-spectral.yaml",
"test": "jest"
},
Expand All @@ -28,6 +29,7 @@
"apache-arrow": "^19.0.0",
"dotenv": "^16.4.7",
"eslint-plugin-jest": "^28.10.0",
"markdown-table": "^3.0.4",
"openapi-to-postmanv2": "4.25.0",
"parquet-wasm": "^0.6.1"
},
Expand Down
60 changes: 60 additions & 0 deletions tools/spectral/ipa/rulesets/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<!--- NOTE: This README file is generated, please see /scripts/generateRulesetReadme.js --->

# IPA Validation Rules

All Spectral rules used in the IPA validation are defined in rulesets grouped by IPA number (`IPA-XXX.yaml`). These rulesets are imported into the main IPA ruleset [ipa-spectral.yaml](https://github.com/mongodb/openapi/blob/main/tools/spectral/ipa/ipa-spectral.yaml) which is used for running the validation.

## Rulesets

The tables below lists all available rules, their descriptions and severity level.

### IPA-005

For rule definitions, see [IPA-005.yaml](https://github.com/mongodb/openapi/blob/main/tools/spectral/ipa/rulesets/IPA-005.yaml).

| Rule Name | Description | Severity |
| --------------------------------------- | ------------------------------------------------------------------------ | -------- |
| xgen-IPA-005-exception-extension-format | IPA exception extensions must follow the correct format. http://go/ipa/5 | warn |

### IPA-102

For rule definitions, see [IPA-102.yaml](https://github.com/mongodb/openapi/blob/main/tools/spectral/ipa/rulesets/IPA-102.yaml).

| Rule Name | Description | Severity |
| ---------------------------------------------------- | -------------------------------------------------------------------------------- | -------- |
| xgen-IPA-102-path-alternate-resource-name-path-param | Paths should alternate between resource names and path params. http://go/ipa/102 | warn |

### IPA-104

For rule definitions, see [IPA-104.yaml](https://github.com/mongodb/openapi/blob/main/tools/spectral/ipa/rulesets/IPA-104.yaml).

| Rule Name | Description | Severity |
| ----------------------------- | --------------------------------------------------------------- | -------- |
| xgen-IPA-104-resource-has-GET | APIs must provide a get method for resources. http://go/ipa/104 | warn |

### IPA-109

For rule definitions, see [IPA-109.yaml](https://github.com/mongodb/openapi/blob/main/tools/spectral/ipa/rulesets/IPA-109.yaml).

| Rule Name | Description | Severity |
| ---------------------------------------------- | ------------------------------------------------------------------------- | -------- |
| xgen-IPA-109-custom-method-must-be-GET-or-POST | The HTTP method for custom methods must be GET or POST. http://go/ipa/109 | warn |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] Golink to internal site. Might be better to have actions self explanatory. Then downstream can link upstream instead.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO I think it's nice to have a link to the IPA itself for each rule, as it will show up when a validation fails. We do use golinks pretty widely in the openapi repo

| xgen-IPA-109-custom-method-must-use-camel-case | The custom method must use camelCase format. http://go/ipa/109 | warn |

### IPA-113

For rule definitions, see [IPA-113.yaml](https://github.com/mongodb/openapi/blob/main/tools/spectral/ipa/rulesets/IPA-113.yaml).

| Rule Name | Description | Severity |
| --------------------------------------- | ------------------------------------------------------------------------------------------- | -------- |
| xgen-IPA-113-singleton-must-not-have-id | Singleton resources must not have a user-provided or system-generated ID. http://go/ipa/113 | warn |

### IPA-123

For rule definitions, see [IPA-123.yaml](https://github.com/mongodb/openapi/blob/main/tools/spectral/ipa/rulesets/IPA-123.yaml).

| Rule Name | Description | Severity |
| ------------------------------------------------- | ------------------------------------------------------- | -------- |
| xgen-IPA-123-enum-values-must-be-upper-snake-case | Enum values must be UPPER_SNAKE_CASE. http://go/ipa/123 | warn |


100 changes: 100 additions & 0 deletions tools/spectral/ipa/scripts/generateRulesetReadme.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import fs from 'node:fs';
import path from 'path';
import { fileURLToPath } from 'url';
import spectral from '@stoplight/spectral-core';
import { markdownTable } from 'markdown-table';
import { loadRuleset } from '../metrics/utils/metricCollectionUtils.js';

const dirname = path.dirname(fileURLToPath(import.meta.url));
const readmeFilePath = path.join(dirname, '../rulesets', 'README.md');

const rulesetsSection = await getRulesetsSection();

const fileContent =
'<!--- NOTE: This README file is generated, please see /scripts/generateRulesetReadme.js --->\n\n' +
'# IPA Validation Rules\n\n' +
'All Spectral rules used in the IPA validation are defined in rulesets grouped by IPA number (`IPA-XXX.yaml`). These rulesets are imported into the main IPA ruleset [ipa-spectral.yaml](https://github.com/mongodb/openapi/blob/main/tools/spectral/ipa/ipa-spectral.yaml) which is used for running the validation.\n\n' +
`${rulesetsSection}` +
'\n';

fs.writeFile(readmeFilePath, fileContent, (error) => {
if (error) {
console.error('Error while generating the IPA rulesets README.md:', error);
process.exit(1);
}
});

async function getRulesetsSection() {
let content =
'## Rulesets\n\n' + 'The tables below lists all available rules, their descriptions and severity level.\n\n';

const rules = await getAllRules();
const ruleNames = Object.keys(rules);
const ipaNumbers = getIpaNumbers(ruleNames);

ipaNumbers.forEach((ipaNumber) => {
const ipaRules = filterRulesByIpaNumber(ipaNumber, rules);
const table = generateRulesetTable(ipaRules);
content +=
`### ${ipaNumber}\n\n` + `For rule definitions, see ${getIpaRulesetUrl(ipaNumber)}.\n\n` + `${table}\n\n`;
});

return content;
}

function generateRulesetTable(rules) {
const table = [['Rule Name', 'Description', 'Severity']];
const tableRows = [];

const ruleNames = Object.keys(rules);
ruleNames.forEach((ruleName) => {
const rule = rules[ruleName];
tableRows.push([ruleName, rule.description, rule.definition.severity]);
});

tableRows.sort(sortBySeverity);
tableRows.forEach((row) => table.push(row));
return markdownTable(table);
}

async function getAllRules() {
const rulesetFilePath = path.join(dirname, '..', 'ipa-spectral.yaml');
const { Spectral } = spectral;
const ruleset = await loadRuleset(rulesetFilePath, new Spectral());
return ruleset.rules;
}

function getIpaNumbers(ruleNames) {
const ipaNumbers = [];
ruleNames.forEach((name) => {
const ipaName = name.substring(5, 12);
if (!ipaNumbers.includes(ipaName)) {
ipaNumbers.push(ipaName);
}
});
return ipaNumbers.sort();
}

function getIpaRulesetUrl(ipaNumber) {
return `[${ipaNumber}.yaml](https://github.com/mongodb/openapi/blob/main/tools/spectral/ipa/rulesets/${ipaNumber}.yaml)`;
}

function filterRulesByIpaNumber(ipaNumber, rules) {
return Object.keys(rules)
.filter((key) => key.includes(ipaNumber))
.reduce((obj, key) => {
return {
...obj,
[key]: rules[key],
};
}, {});
}

function sortBySeverity(a, b) {
if (a[2] < b[2]) {
return -1;
} else if (a[2] > b[2]) {
return 1;
}
return 0;
}
Loading