Skip to content

Commit

Permalink
feat(ibm-no-unsupported-keywords): add new validation rule
Browse files Browse the repository at this point in the history
This commit introduces the new 'ibm-no-unsupported-keywords'
validation rule which checks to make sure that the
(currently) unsupported keywords 'jsonSchemaDialect' and
'webhooks' are not present in an OpenAPI 3.1 document.

Signed-off-by: Phil Adams <[email protected]>
  • Loading branch information
padamstx committed Sep 27, 2023
1 parent 1b15f4c commit 821c0d8
Show file tree
Hide file tree
Showing 7 changed files with 232 additions and 0 deletions.
77 changes: 77 additions & 0 deletions docs/ibm-cloud-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ which is delivered in the `@ibm-cloud/openapi-ruleset` NPM package.
* [ibm-no-operation-requestbody](#ibm-no-operation-requestbody)
* [ibm-no-optional-properties-in-required-body](#ibm-no-optional-properties-in-required-body)
* [ibm-no-space-in-example-name](#ibm-no-space-in-example-name)
* [ibm-no-unsupported-keywords](#ibm-no-unsupported-keywords)
* [ibm-openapi-tags-used](#ibm-openapi-tags-used)
* [ibm-operation-summary](#ibm-operation-summary)
* [ibm-operationid-casing-convention](#ibm-operationid-casing-convention)
Expand Down Expand Up @@ -343,6 +344,12 @@ should probably be required instead of optional.</td>
<td>oas3</td>
</tr>
<tr>
<td><a href="#ibm-no-unsupported-keywords">ibm-no-unsupported-keywords</a></td>
<td>error</td>
<td>Checks for the use of unsupported keywords within an OpenAPI 3.1.x document.</td>
<td>oas3_1</td>
</tr>
<tr>
<td><a href="#ibm-openapi-tags-used">ibm-openapi-tags-used</a></td>
<td>warn</td>
<td>Verifies that each defined tag is referenced by at least one operation.</td>
Expand Down Expand Up @@ -3432,6 +3439,76 @@ paths:
</table>


### ibm-no-unsupported-keywords
<table>
<tr>
<td><b>Rule id:</b></td>
<td><b>ibm-no-unsupported-keywords</b></td>
</tr>
<tr>
<td valign=top><b>Description:</b></td>
<td>This rule checks for the presence of specific keywords within an OpenAPI 3.1.x document that are not yet supported
by IBM's SDK-related tooling - specifically the <code>jsonSchemaDialect</code> and
<code>webhooks</code> keywords. An error is logged if either of these keywords is found in the document.
</td>
</tr>
<tr>
<td><b>Severity:</b></td>
<td>error</td>
</tr>
<tr>
<td><b>OAS Versions:</b></td>
<td>oas3_1</td>
</tr>
<tr>
<td valign=top><b>Non-compliant example:<b></td>
<td>
<pre>
openapi: 3.1.0
info:
title: Thing Service
description: A service that manages Things
version: 1.0.0
jsonSchemaDialect: 'https://spec.openapis.org/oas/3.1/dialect/base' &lt;&lt;&lt; not supported
webhooks: &lt;&lt;&lt; not supported
newThingTypeAvailable:
post:
description: |-
A callback-like operation to be implemented by the client so that it
can be informed of a new type of Thing supported by the server.
requestBody:
description: 'A new type of Thing can now be created on the server.'
content:
application/json:
schema:
type: object,
properties:
thing_type:
description: 'The new type value that can be used to create a Thing instance.'
type: string
responses:
'200':
description: |-
Return a 200 status code to the server to indicate that the new Thing type
was received successfully by the client.
</pre>
</td>
</tr>
<tr>
<td valign=top><b>Compliant example:</b></td>
<td>
<pre>
openapi: 3.1.0
info:
title: Thing Service
description: A service that manages Things
version: 1.0.0
</pre>
</td>
</tr>
</table>


### ibm-openapi-tags-used
<table>
<tr>
Expand Down
1 change: 1 addition & 0 deletions packages/ruleset/src/functions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ module.exports = {
noAmbiguousPaths: require('./no-ambiguous-paths'),
noNullableProperties: require('./no-nullable-properties'),
noOperationRequestBody: require('./no-operation-requestbody'),
noUnsupportedKeywords: require('./no-unsupported-keywords'),
operationIdCasingConvention: require('./operationid-casing-convention'),
operationIdNamingConvention: require('./operationid-naming-convention'),
operationSummaryExists: require('./operation-summary-exists'),
Expand Down
53 changes: 53 additions & 0 deletions packages/ruleset/src/functions/no-unsupported-keywords.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* Copyright 2017 - 2023 IBM Corporation.
* SPDX-License-Identifier: Apache2.0
*/

const { LoggerFactory } = require('../utils');

let ruleId;
let logger;

const ErrorMsg =
'An unsupported OpenAPI 3.1 keyword was found in the OpenAPI document:';

module.exports = function (apidef, _opts, context) {
if (!logger) {
ruleId = context.rule.name;
logger = LoggerFactory.getInstance().getLogger(ruleId);
}
return noUnsupportedKeywords(apidef);
};

/**
* If 'unevaluatedProperties' is specified within "schema" then it must be set to false.
*
* @param {*} apidef the API definition object
* @returns an array of zero or more errors
*/
function noUnsupportedKeywords(apidef) {
logger.debug(`${ruleId}: checking for unsupported OpenAPI 3.1 keywords`);

const errors = [];

if ('jsonSchemaDialect' in apidef) {
logger.debug(`${ruleId}: found 'jsonSchemaDialect'`);
errors.push({
message: `${ErrorMsg} jsonSchemaDialect`,
path: ['jsonSchemaDialect'],
});
}

if ('webhooks' in apidef) {
logger.debug(`${ruleId}: found 'webhooks`);
errors.push({
message: `${ErrorMsg} webhooks`,
path: ['webhooks'],
});
}

if (!errors.length) {
logger.debug(`${ruleId}: PASSED!`);
}
return errors;
}
1 change: 1 addition & 0 deletions packages/ruleset/src/ibm-oas.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ module.exports = {
'ibm-no-operation-requestbody': ibmRules.noOperationRequestBody,
'ibm-no-optional-properties-in-required-body': ibmRules.optionalRequestBody,
'ibm-no-space-in-example-name': ibmRules.examplesNameContainsSpace,
'ibm-no-unsupported-keywords': ibmRules.noUnsupportedKeywords,
'ibm-openapi-tags-used': ibmRules.unusedTags,
'ibm-operation-summary': ibmRules.operationSummaryExists,
'ibm-operationid-casing-convention': ibmRules.operationIdCasingConvention,
Expand Down
1 change: 1 addition & 0 deletions packages/ruleset/src/rules/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ module.exports = {
noAmbiguousPaths: require('./no-ambiguous-paths'),
noNullableProperties: require('./no-nullable-properties'),
noOperationRequestBody: require('./no-operation-requestbody'),
noUnsupportedKeywords: require('./no-unsupported-keywords'),
operationSummaryExists: require('./operation-summary-exists'),
optionalRequestBody: require('./optional-request-body'),
paginationStyle: require('./pagination-style'),
Expand Down
20 changes: 20 additions & 0 deletions packages/ruleset/src/rules/no-unsupported-keywords.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Copyright 2017 - 2023 IBM Corporation.
* SPDX-License-Identifier: Apache2.0
*/

const { oas3_1 } = require('@stoplight/spectral-formats');
const { noUnsupportedKeywords } = require('../functions');

module.exports = {
description:
'Verifies that unsupported OpenAPI 3.1 keywords are not used in the API document.',
message: '{{error}}',
given: ['$'],
severity: 'error',
formats: [oas3_1],
resolved: false,
then: {
function: noUnsupportedKeywords,
},
};
79 changes: 79 additions & 0 deletions packages/ruleset/test/no-unsupported-keywords.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* Copyright 2017 - 2023 IBM Corporation.
* SPDX-License-Identifier: Apache2.0
*/

const { noUnsupportedKeywords } = require('../src/rules');
const { makeCopy, rootDocument, testRule, severityCodes } = require('./utils');

const rule = noUnsupportedKeywords;
const ruleId = 'ibm-no-unsupported-keywords';
const expectedSeverity = severityCodes.error;
const expectedMsgPrefix =
'An unsupported OpenAPI 3.1 keyword was found in the OpenAPI document:';

describe(`Spectral rule: ${ruleId}`, () => {
beforeAll(() => {
rootDocument.openapi = '3.1.0';
});

describe('Should not yield errors', () => {
it('Clean spec - no unsupported keywords present', async () => {
const results = await testRule(ruleId, rule, rootDocument);
expect(results).toHaveLength(0);
});
});

describe('Should yield errors', () => {
it('jsonSchemaDialect present', async () => {
const testDocument = makeCopy(rootDocument);

testDocument.jsonSchemaDialect =
'https://spec.openapis.org/oas/3.1/dialect/base';

const results = await testRule(ruleId, rule, testDocument);
expect(results).toHaveLength(1);

const expectedPaths = ['jsonSchemaDialect'];
for (let i = 0; i < results.length; i++) {
expect(results[i].code).toBe(ruleId);
expect(results[i].message).toBe(
`${expectedMsgPrefix} jsonSchemaDialect`
);
expect(results[i].severity).toBe(expectedSeverity);
expect(results[i].path.join('.')).toBe(expectedPaths[i]);
}
});
it('webhooks present', async () => {
const testDocument = makeCopy(rootDocument);

testDocument.webhooks = {
newDrinkAvailable: {
post: {
requestBody: {
description: 'A new brand of beer is available for consumption.',
content: {
'application/beer': {
schema: {
type: 'string',
},
},
},
},
},
},
};

const results = await testRule(ruleId, rule, testDocument);
expect(results).toHaveLength(1);

const expectedPaths = ['webhooks'];
for (let i = 0; i < results.length; i++) {
expect(results[i].code).toBe(ruleId);
expect(results[i].message).toBe(`${expectedMsgPrefix} webhooks`);
expect(results[i].severity).toBe(expectedSeverity);
expect(results[i].path.join('.')).toBe(expectedPaths[i]);
}
});
});
});

0 comments on commit 821c0d8

Please sign in to comment.