@ibm-cloud/openapi-ruleset
Version:
Spectral ruleset for validating IBM Cloud services
160 lines (142 loc) • 4.75 kB
JavaScript
/**
* Copyright 2017 - 2023 IBM Corporation.
* SPDX-License-Identifier: Apache2.0
*/
const {
isStringSchema,
schemaHasConstraint,
} = require('@ibm-cloud/openapi-ruleset-utilities');
const { LoggerFactory } = require('../utils');
let ruleId;
let logger;
module.exports = function (pathParam, _opts, context) {
if (!logger) {
ruleId = context.rule.name;
logger = LoggerFactory.getInstance().getLogger(ruleId);
}
return pathParameterNotCRN(pathParam, context.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 pathParameterNotCRN(pathParam, path) {
logger.debug(
`${ruleId}: checking path parameter '${
pathParam.name
}' at location: ${path.join('.')}`
);
const subPath = isCRNParameter(pathParam);
if (subPath && subPath.length > 0) {
logger.debug(`${ruleId}: it's a CRN value!`);
return [
{
message: 'Path parameters 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) &&
schemaHasConstraint(schema, s => s.format === 'crn')
) {
return ['schema', 'format'];
}
// Check if the schema defines a pattern field that starts with "crn" or "^crn".
if (
schema &&
schemaHasConstraint(
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)
);
}