Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export default [
},
},
{
ignores: ['node-modules'],
ignores: ['node_modules'],
},
{
files: ['**/*.test.js'],
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"scripts": {
"format": "npx prettier . --write",
"format-check": "npx prettier . --check",
"lint-js": "npx eslint **/*.js",
"lint-js": "npx eslint .",
"gen-ipa-docs": "node tools/spectral/ipa/scripts/generateRulesetReadme.js",
"ipa-validation": "spectral lint ./openapi/.raw/v2.yaml --ruleset=./tools/spectral/ipa/ipa-spectral.yaml",
"ipa-filter-violations": "node tools/spectral/ipa/scripts/filter-ipa-violations.js",
Expand Down
11 changes: 11 additions & 0 deletions tools/spectral/.spectral.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,18 @@ aliases:
- "#OperationObject.parameters[?(@ && @.in)]"
- "$.components.schemas[*]..properties[?(@ && @.type)]"

functions:
- acceptHeaderUpcomingVersionLimit

rules:
accept-header-upcoming-version-limit:
description: Ensure that each operation has at most one upcoming API Accept header.
message: "An operation must not have more than one upcoming API Accept header (format: application/vnd.atlas.YYYY-MM-DD.upcoming+format)."
severity: error
given: $.paths[*][*]
then:
function: "acceptHeaderUpcomingVersionLimit"

xgen-schema-name-pascal-case:
description: OpenAPI Schema names should use PascalCase. PascalCase ensures consistency with OpenAPI generated code.
message: "`{{property}}` name must follow PascalCase. Please verify if you have provided valid @Schema(name='') annotation"
Expand Down
1 change: 1 addition & 0 deletions tools/spectral/.tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodejs 24.0.2
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { DiagnosticSeverity } from '@stoplight/types';
import acceptHeaderUpcomingVersionLimit from '../functions/acceptHeaderUpcomingVersionLimit';

describe('accept-header-upcoming-version-limit', () => {
it('valid: no upcoming Accept headers', () => {
const operation = {
operationId: 'getTest',
responses: {
200: {
content: {
'application/json': {},
},
},
},
requestBody: {
content: {
'application/json': {},
},
},
};

const result = acceptHeaderUpcomingVersionLimit(operation);
expect(result).toBeUndefined();
});

it('valid: one upcoming Accept header in response', () => {
const operation = {
operationId: 'getTest',
responses: {
200: {
content: {
'application/vnd.atlas.2024-06-01.upcoming+json': {},
'application/json': {},
},
},
},
};

const result = acceptHeaderUpcomingVersionLimit(operation);
expect(result).toBeUndefined();
});

it('invalid: two upcoming Accept headers in response', () => {
const operation = {
operationId: 'getTest',
responses: {
200: {
content: {
'application/vnd.atlas.2024-06-01.upcoming+json': {},
'application/vnd.atlas.2024-07-01.upcoming+json': {},
'application/json': {},
},
},
},
};

const result = acceptHeaderUpcomingVersionLimit(operation);
expect(result).toEqual([
{
message: expect.stringMatching(/Found 2 upcoming API Accept headers/),
},
]);
});

it('invalid: two upcoming Accept headers in request', () => {
const operation = {
operationId: 'postTest',
requestBody: {
content: {
'application/vnd.atlas.2024-06-01.upcoming+json': {},
'application/vnd.atlas.2024-07-01.upcoming+json': {},
'application/json': {},
},
},
};

const result = acceptHeaderUpcomingVersionLimit(operation);
expect(result).toEqual([
{
message: expect.stringMatching(/Found 2 upcoming API Accept headers/),
},
]);
});

it('invalid: missing operationId', () => {
const operation = {
responses: {
200: {
content: {
'application/vnd.atlas.2024-06-01.upcoming+json': {},
},
},
},
};

const result = acceptHeaderUpcomingVersionLimit(operation);
expect(result).toBeUndefined();
});
});
41 changes: 41 additions & 0 deletions tools/spectral/functions/acceptHeaderUpcomingVersionLimit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
module.exports = function (input) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

can we have unit tests like we have for ipa rule sets?

https://github.com/mongodb/openapi/tree/main/tools/spectral/ipa/__tests__

Copy link
Member Author

Choose a reason for hiding this comment

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

I missed those, didn't know it was possible! Thanks

// Get operationId from context, if this fails return an error
const operationId = input.operationId;
if (!operationId) {
return;
}

// List of errors
const errors = [];

// Validate versions in 200 responses
const responseErr = validateContent(operationId, 'response', input?.responses?.[200]?.content);
if (responseErr != null) errors.push(responseErr);

// Validate versions in requests
const requestErr = validateContent(operationId, 'request', input?.requestBody?.content);
if (requestErr != null) errors.push(requestErr);

return errors.length > 0 ? errors : undefined;
};

// Check for upcoming API Accept headers
const upcomingRegex = /^application\/vnd\.atlas\.\d{4}-\d{2}-\d{2}\.upcoming\+.+$/;

function validateContent(operationId, section, content) {
if (content == null) {
return null;
}

const contentTypes = Object.keys(content);
const upcomingContentTypes = contentTypes.filter((k) => upcomingRegex.test(k));
// If there's less than or equal to one upcoming header then the operation is valid
if (upcomingContentTypes.length <= 1) {
return null;
}

// Return an error message
return {
message: `OperationId: ${operationId} - Found ${upcomingContentTypes.length} upcoming API Accept headers (section: ${section}): ${upcomingContentTypes.join(', ')}`,
};
}
Loading