functions/version-policy.js (90 lines of code) (raw):
// Check:
// - DO NOT include a version segment in the base_url or path
// Return the first segment of a path that matches the pattern 'v\d+' or 'v\d+.\d+
function getVersion(path) {
const url = new URL(path, 'https://foo.bar');
const segments = url.pathname.split('/');
return segments.find((segment) => segment.match(/v[0-9]+(.[0-9]+)?/));
}
function checkPaths(targetVal) {
const oas2 = targetVal.swagger;
if (oas2) {
const basePath = targetVal.basePath || '';
const version = getVersion(basePath);
if (version) {
return [
{
message: `Version segment "${version}" in basePath violates Azure versioning policy.`,
path: ['basePath'],
},
];
}
}
// We did not find a major version in basePath, so now check the paths
const { paths } = targetVal;
const errors = [];
if (paths && typeof paths === 'object') {
Object.keys(paths).forEach((path) => {
const version = getVersion(path);
if (version) {
errors.push({
message: `Version segment "${version}" in path violates Azure versioning policy.`,
path: ['paths', path],
});
}
});
}
return errors;
}
function findVersionParam(params) {
const isApiVersion = (elem) => elem.name === 'api-version' && elem.in === 'query';
if (params && Array.isArray(params)) {
return params.filter(isApiVersion).shift();
}
return undefined;
}
// Verify version parameter has certain characteristics:
// - it is required
function validateVersionParam(param, path) {
const errors = [];
if (!param.required) {
errors.push({
message: '"api-version" should be a required parameter',
path,
});
}
return errors;
}
// Verify that every operation defines a query param called `api-version`
function checkVersionParam(targetVal) {
const { paths } = targetVal;
const errors = [];
if (paths && typeof paths === 'object') {
Object.keys(paths).forEach((path) => {
// Parameters can be defined at the path level.
if (paths[path].parameters && Array.isArray(paths[path].parameters)) {
const versionParam = findVersionParam(paths[path].parameters);
if (versionParam) {
const index = paths[path].parameters.indexOf(versionParam);
errors.push(...validateVersionParam(versionParam, ['paths', path, 'parameters', index.toString()]));
return;
}
}
['get', 'post', 'put', 'patch', 'delete'].forEach((method) => {
if (paths[path][method]) {
const versionParam = findVersionParam(paths[path][method].parameters);
if (versionParam) {
const index = paths[path][method].parameters.indexOf(versionParam);
errors.push(...validateVersionParam(versionParam, ['paths', path, method, 'parameters', index]));
} else {
errors.push({
message: 'Operation does not define an "api-version" query parameter.',
path: ['paths', path, method, 'parameters'],
});
}
}
});
});
}
return errors;
}
// Check API definition to ensure conformance to Azure versioning guidelines.
// @param targetVal - the entire API document
module.exports = (targetVal) => {
if (targetVal === null || typeof targetVal !== 'object') {
return [];
}
const errors = checkPaths(targetVal);
errors.push(...checkVersionParam(targetVal));
return errors;
};