Skip to content

Commit

Permalink
feat(path-param-not-crn): add new spectral-style rule
Browse files Browse the repository at this point in the history
This commit introduces the new spectral-style
'path-param-not-crn' validation rule which will verify
that there are no path parameters that are defined as
a CRN (Cloud Resource Name) value.

Signed-off-by: Phil Adams <[email protected]>
  • Loading branch information
padamstx committed Sep 15, 2022
1 parent d649565 commit efea0e5
Show file tree
Hide file tree
Showing 7 changed files with 404 additions and 0 deletions.
97 changes: 97 additions & 0 deletions docs/ibm-cloud-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ which is delivered in the `@ibm-cloud/openapi-ruleset` NPM package.
* [Rule: parameter-order](#rule-parameter-order)
* [Rule: parameter-schema-or-content](#rule-parameter-schema-or-content)
* [Rule: patch-request-content-type](#rule-patch-request-content-type)
* [Rule: path-param-not-crn](#rule-path-param-not-crn)
* [Rule: path-segment-case-convention](#rule-path-segment-case-convention)
* [Rule: precondition-header](#rule-precondition-header)
* [Rule: prohibit-summary-sentence-style](#rule-prohibit-summary-sentence-style)
Expand Down Expand Up @@ -366,6 +367,12 @@ or <code>application/merge-patch+json</code>.</td>
<td>oas3</td>
</tr>
<tr>
<td><a href="#rule-path-param-not-crn">path-param-not-crn</a></td>
<td>warn</td>
<td>Verifies that path parameters are not defined as CRN (Cloud Resource Name) values</td>
<td>oas2, oas3</td>
</tr>
<tr>
<td><a href="#rule-path-segment-case-convention">path-segment-case-convention</a></td>
<td>error</td>
<td>Path segments must follow a specific case convention</td>
Expand Down Expand Up @@ -3349,6 +3356,96 @@ paths:
</table>


### Rule: path-param-not-crn
<table>
<tr>
<td><b>Rule id:</b></td>
<td><b>path-param-not-crn</b></td>
</tr>
<tr>
<td valign=top><b>Description:</b></td>
<td>This rule checks to make sure that there are no path parameters that are defined as a CRN (Cloud Resource Name) value.
<p>In order to determine whether or not a path parameter is considered to be defined as a CRN value, this validation rule
will perform the following checks:
<ul>
<li>The parameter's <code>name</code> field contains "crn" (e.g. "resource_crn")</li>
<li>The parameter's schema is defined with type=string, format=crn</li>
<li>The parameter's schema is defined with a pattern field that starts with either "crn" or "^crn" (e.g. 'crn:[-0-9A-Fa-f]+')</li>
<li>The parameter's <code>example</code> field contains a CRN-like value (e.g. "crn:0afd-0138-2636")</li>
<li>The parameter's <code>examples</code> field contains an entry containing a CRN-like value, as in this example:
<pre>
</pre>
components:
parameters:
ThingIdParam:
name: thing_id
description: The id of the Thing instance
in: path
required: true
schema:
type: string
examples:
crn_example:
value: 'crn:0afd-0138-2636'
</li>
<li>The parameter schema's <code>example</code> field contains a CRN-like value (e.g. "crn:0afd-0138-2636")</li>
<li>The parameter's <code>description</code> field contains either "CRN" or "Cloud Resource Name"</li>
<li>The parameter schema's <code>description</code> field contains either "CRN" or "Cloud Resource Name"</li>
</ul>
These checks are logically OR'd together, so that if any one or more of these checks
are true for a particular parameter, then a warning is raised for that parameter.
</tr>
<tr>
<td><b>Severity:</b></td>
<td>warn</td>
</tr>
<tr>
<td><b>OAS Versions:</b></td>
<td>oas2, oas3</td>
</tr>
<tr>
<td valign=top><b>Non-compliant example:<b></td>
<td>
<pre>
components:
parameters:
ThingCrnParam:
name: thing_crn
description: The CRN associated with the Thing instance
in: path
required: true
schema:
type: string
format: crn
pattern: '^crn:[-0-9A-Fa-f]+$'
minLength: 5
maxLength: 32
</pre>
</td>
</tr>
<tr>
<td valign=top><b>Compliant example:</b></td>
<td>
<pre>
components:
parameters:
ThingIdParam:
name: thing_id
description: The id associated with the Thing instance
in: path
required: true
schema:
type: string
format: identifier
pattern: '^id:[-0-9A-Fa-f]+$'
minLength: 5
maxLength: 32
</pre>
</td>
</tr>
</table>


### Rule: path-segment-case-convention
<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 @@ -28,6 +28,7 @@ module.exports = {
parameterDescription: require('./parameter-description'),
parameterOrder: require('./parameter-order'),
patchRequestContentType: require('./patch-request-content-type'),
pathParamNotCRN: require('./path-param-not-crn'),
pathSegmentCaseConvention: require('./path-segment-case-convention'),
preconditionHeader: require('./precondition-header'),
propertyAttributes: require('./property-attributes'),
Expand Down
140 changes: 140 additions & 0 deletions packages/ruleset/src/functions/path-param-not-crn.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
const {
isStringSchema,
checkCompositeSchemaForConstraint
} = require('../utils');

module.exports = function(pathParam, _opts, { path }) {
return pathParamNotCRN(pathParam, path);
};

/**
* This function will check "pathParam" (assumed to be a path parameter object)
* to make sure that it is not defined as a "CRN" (Cloud Resource Name) value.
* @param {*} pathParam the path parameter object to check
* @param {*} path the jsonpath location of "pathParam" within the API definition
* @returns an array containing the violations found or [] if no violations
*/
function pathParamNotCRN(pathParam, path) {
const subPath = isCRNParameter(pathParam);
if (subPath && subPath.length > 0) {
return [
{
message: 'Path parameter should not be defined as a CRN value',
path: [...path, ...subPath]
}
];
}

return [];
}

/**
* This function checks to see if the specified parameter object "p" is defined as a CRN-like value.
* If "p" does not appear to be defined as a CRN-like value, then [] is returned.
* If "p" does appear to be defined as a CRN-like value, then the return value will be a
* list of one or more jsonpath segments that represent the relative location of the violation
* (relative to "p"'s location within the API definition)
* @param {} p the parameter object to check
* @returns a list of zero or more jsonpath segments to indicate that a violation
* was found at that relative location
*/
function isCRNParameter(p) {
// Check if the parameter name contains "crn".
if (
p.name &&
typeof p.name === 'string' &&
/crn/.test(p.name.toLowerCase())
) {
return ['name'];
}

// Grab the parameter's schema object.
let schema = p.schema;
if (!schema) {
// If not set directly on the parameter, grab the first schema within the content map instead.
if (p.content && typeof p.content === 'object' && p.content.size > 0) {
schema = p.content.values()[0];
}
}

// Check if the schema is defined as type=string/format=crn.
if (
schema &&
isStringSchema(schema) &&
checkCompositeSchemaForConstraint(schema, s => s.format === 'crn')
) {
return ['schema', 'format'];
}

// Check if the schema defines a pattern field that starts with "crn" or "^crn".
if (
schema &&
checkCompositeSchemaForConstraint(
schema,
s =>
s.pattern &&
typeof s.pattern === 'string' &&
/^\^?crn.*/.test(s.pattern.trim().toLowerCase())
)
) {
return ['schema', 'pattern'];
}

// Check if the parameter's "example" field is a CRN-like value.
if (p.example && isCRNValue(p.example)) {
return ['example'];
}

// Check if the parameter's "examples" field contains an entry with a CRN-like value.
if (p.examples && typeof p.examples === 'object') {
for (const [name, obj] of Object.entries(p.examples)) {
if (obj && obj.value && isCRNValue(obj.value)) {
return ['examples', name, 'value'];
}
}
}

// Check if the parameter schema's "example" field is a CRN-like value.
if (schema && schema.example && isCRNValue(schema.example)) {
return ['schema', 'example'];
}

// Check if the parameter's description contains "CRN" or "Cloud Resource Name".
if (isCRNInDescription(p)) {
return ['description'];
}

// Check if the parameter schema's description contains "CRN" or "Cloud Resource Name".
if (isCRNInDescription(schema)) {
return ['schema', 'description'];
}

// If we made it through the gauntlet unscathed, then return an empty list to
// indicate no violations.
return [];
}

/**
* Returns true iff the specified example value appears to be an example of
* a CRN value.
* @param {} value the value to check
* @return boolean
*/
function isCRNValue(value) {
return value && typeof value === 'string' && /^crn:.*/.test(value.trim());
}

/**
* Returns true iff "obj" is an object with a "description" field that contains
* "CRN" or "Cloud Resource Name".
* @param {*} obj the objet whose "description" field should be checked
* @return boolean
*/
function isCRNInDescription(obj) {
return (
obj &&
obj.description &&
typeof obj.description === 'string' &&
/^.*((CRN)|(Cloud\s*Resource\s*Name)).*$/.test(obj.description)
);
}
1 change: 1 addition & 0 deletions packages/ruleset/src/ibm-oas.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ module.exports = {
'parameter-order': ibmRules.parameterOrder,
'parameter-schema-or-content': ibmRules.parameterSchemaOrContent,
'patch-request-content-type': ibmRules.patchRequestContentType,
'path-param-not-crn': ibmRules.pathParamNotCRN,
'path-segment-case-convention': ibmRules.pathSegmentCaseConvention,
'precondition-header': ibmRules.preconditionHeader,
'prohibit-summary-sentence-style': ibmRules.prohibitSummarySentenceStyle,
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 @@ -39,6 +39,7 @@ module.exports = {
parameterOrder: require('./parameter-order'),
parameterSchemaOrContent: require('./parameter-schema-or-content'),
patchRequestContentType: require('./patch-request-content-type'),
pathParamNotCRN: require('./path-param-not-crn'),
pathSegmentCaseConvention: require('./path-segment-case-convention'),
preconditionHeader: require('./precondition-header'),
prohibitSummarySentenceStyle: require('./prohibit-summary-sentence-style'),
Expand Down
18 changes: 18 additions & 0 deletions packages/ruleset/src/rules/path-param-not-crn.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const { oas2, oas3 } = require('@stoplight/spectral-formats');
const { pathParamNotCRN } = require('../functions');

module.exports = {
description:
'Path parameter should not be defined as a CRN (Cloud Resource Name) value',
message: '{{error}}',
formats: [oas2, oas3],
given: [
'$.paths[*].parameters[?(@.in === "path")]',
'$.paths[*][get,put,post,delete,options,head,patch,trace].parameters[?(@.in === "path")]'
],
severity: 'warn',
resolved: true,
then: {
function: pathParamNotCRN
}
};
Loading

0 comments on commit efea0e5

Please sign in to comment.