Skip to content

Commit

Permalink
Merge pull request #30 from CiscoDevNet/DT_add-exceptions-to-get-vers…
Browse files Browse the repository at this point in the history
…ion-checker

Updates to the multi-version fonction and 'Contract' ruleset
  • Loading branch information
ObjectIsAdvantag authored Oct 4, 2024
2 parents 91fbb17 + 2d937ec commit c1a6b30
Show file tree
Hide file tree
Showing 5 changed files with 240 additions and 18 deletions.
11 changes: 7 additions & 4 deletions contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ export default {
},
},
'success-status-code': {
'description': 'For every operation in the OAS document, there should be at least one success status code defined. A successful status code is in the 1xx, 2xx or 3xx range series, and generally a 200, 201 or 204.',
'description': 'For every operation in the OAS document, there should be at least one success status code defined. A successful status code is in the 1xx, 2xx or 3xx range series, and generally a 200, 201 or 204.',
'message': '{{description}}; {{error}}',
'severity': 'error',
'given': '$.paths.*.*.responses',
Expand All @@ -125,7 +125,7 @@ export default {
},
},
'error-status-code': {
'description': 'There should be at least one error status code either 4xx or 5xx.',
'description': 'responses should include at least one error status code (4xx or 5xx)',
'message': '{{description}}; {{error}}',
'severity': 'warn',
'given': '$.paths.*.*.responses',
Expand Down Expand Up @@ -163,7 +163,7 @@ export default {
},
},
'multi-versions-server-url-missing-version': {
'description': 'No version is specified in the server object of the OpenAPI Document. Best practices recommend specifying the version in the server object only once',
'description': 'path should not have version while server object has one',
'message': '{{description}}; {{error}}',
'severity': 'warn',
'given': [
Expand All @@ -172,7 +172,7 @@ export default {
'then': {
'function': multiVersion,
'functionOptions': {
'check': 'server-url-missing',
'check': 'server-url-missing'
},
},
},
Expand All @@ -185,6 +185,9 @@ export default {
],
'then': {
'function': multiVersion,
'functionOptions': {
'exceptions' : [ "networks/{networkId}/switch/dhcp/v4" ]
},
},
},
'operationId-required-and-unique': {
Expand Down
27 changes: 13 additions & 14 deletions functions/multiVersion.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
'use strict';

import { isObject } from '../util/funcUtils.js';
import getVersion from '../util/getVersion.js';


/**
* Checks if there is only one version in server urls and paths.
Expand Down Expand Up @@ -78,16 +80,24 @@ export default function (targetVal, opts) {
let pathFirstVersion = '';

for (const { path } of getAllPaths(paths)) {
const version = getVersion(path);

// Is there a version identified on the path, considering the path fragments exceptions if any
let version;
if (opts && opts.exceptions) {
version = getVersion(path, opts.exceptions);
}
else {
version = getVersion(path);
}
if (version === '') {
continue;
}

// Check if the version detected is acceptable
if (pathFirstVersion === '') {
if (serverFirstVersion !== '') {
results.push({
message: 'path should not have version while server object has one.',
message: `path should not have version while server object has one.`,
path: ['paths', path],
});
}
Expand All @@ -97,7 +107,7 @@ export default function (targetVal, opts) {

if (pathFirstVersion !== version) {
results.push({
message: 'multi versions in paths.',
message: `multi versions in paths.`,
path: ['paths', path],
});
}
Expand Down Expand Up @@ -155,14 +165,3 @@ function* getAllServerURLs(doc) {
yield item;
}
}

function getVersion(str) {
let version = '';
const parts = str.match(/v\d+[^/]*/);

if (parts) {
version = parts[0];
}

return version;
}
11 changes: 11 additions & 0 deletions test/CHANGELOG
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Changelog

## October 24th, 2024

The 'multi-version' rule received several updates:
- it does not consider any more 'v*' patterns in path such as 'ipv6' as version number.
- the rule also support expections for paths such as '/api/dhcp/v4' where v4 should not be considered as a version number per the API design.

The 'Contract' ruleset has been updated to include an exception for path "networks/{networkId}/switch/dhcp/v4" of the Meraki API.


29 changes: 29 additions & 0 deletions util/getVersion.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@

export default function getVersion(str, exceptionList) {

// Exclude paths fragments that are considered exceptions
if (exceptionList) {
for (const exception of exceptionList) {
if (str.includes(exception)) {
str = str.replace(exception, 'EXCEPTION');
}
}
}

// The version regex is pretty complex to address all use cases at Cisco
// Please check this Asana issue for full context and explorations: https://app.asana.com/0/1206872740398257/1206657704786474
//const versionRegex = /\b\/?v\d+(\.\d+)*\/?(\b|$)/; // does not fully work, captures v6 as a version number for path: "/device/cedgecflowd/app-fwd-cflowd-v6-flows"
const versionRegex = /(?<![-\w])\/?v\d+(\.\d+)*\/?(\b|$)/; // does NOT capture v6 as a version number for path: "/device/cedgecflowd/app-fwd-cflowd-v6-flows"

let version = '';
const parts = str.match(versionRegex);
if (parts) {
version = parts[0];

// The latest regexp captures /v1 in some cases, let's remove leading or trailing '/' if any
version = version.charAt(0) === '/' ? version.slice(1) : version;
version = version.charAt(version.length - 1) === '/' ? version.slice(0, -1) : version;
}

return version;
}
180 changes: 180 additions & 0 deletions util/getVersion.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/**
* Copyright 2022 Cisco Systems, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/

import getVersion from './getVersion.js';
import { describe, expect, jest, test } from '@jest/globals';

describe('getVersion', () => {
const serverUrls = [
{
url: 'http://api.example1.com/v1.2/',
version: 'v1.2'
},
{
url: 'http://api.example1.com/v1',
version: 'v1'
}
]

const paths = [
{
path: 'v2/my/bad/path',
version: 'v2'
},
{
path: '/v2.1.0/my/path',
version: 'v2.1.0'
},
{
path: '/api/v1/cloudonramp/saas',
version: 'v1'
},
{
path: '/api/v1/config-group',
version: 'v1'
},
{
path: '/api/v1/config-group/{configGroupId}',
version: 'v1'
},
{
path: '/api/v2/data/device/statistics/interfacestatistics/fields',
version: 'v2'
},
{
path: '/api/device/dhcpv6/intesface',
version: ''
},
{
path: '/api/v2.1.0/device/dhcpv6/intesface',
version: 'v2.1.0'
},
{
path: '/api/device/interface/ipv6Stats',
version: ''
},
{
path: '/api/device/ip/v4fib',
version: ''
},
{
path: '/api/device/ip/v6fib',
version: ''
},
{
path: '/api/device/ipsec/ikev1',
version: ''
},
{
path: '/api/device/ipsec/ikev2',
version: ''
},
{
path: '/api/device/ipv6/nd6',
version: ''
},
{
path: '/api/device/ndv6',
version: ''
},
{
path: '/api/device/omp/routes/advertised/ompIpv6',
version: ''
},
{
path: '/api/device/omp/routes/received/ompIpv6',
version: ''
},
{
path: '/api/device/ospf/v3interface',
version: ''
},
{
path: '/api/device/ospf/v3neighbor',
version: ''
},
{
path: '/api/device/policy/ipv6/accesslistassociations',
version: ''
},
{
path: '/api/device/policy/ipv6/accesslistcounters',
version: ''
},
{
path: '/api/device/policy/ipv6/accesslistnames',
version: ''
},
{
path: '/api/device/policy/ipv6/accesslistpolicers',
version: ''
},
{
path: '/api/device/roleBasedIpv6Counters',
version: ''
},
{
path: '/api/device/roleBasedIpv6Permissions',
version: ''
},
{
path: '/api/template/policy/definition/aclv6',
version: ''
},
{
path: '/api/template/policy/definition/aclv6/bulk',
version: ''
},
{
path: '/api/template/policy/definition/aclv6/multiple/{id}',
version: ''
},
{
path: '/api/template/policy/definition/aclv6/{id}',
version: ''
},
{
path: '/api/template/policy/definition/deviceaccesspolicyv6',
version: ''
},
{
path: '/api/template/policy/definition/deviceaccesspolicyv6/bulk',
version: ''
},
{
path: '/api/template/policy/definition/deviceaccesspolicyv6/multiple/{id}',
version: ''
}
]


test('should return the version from the server urls', () => {
serverUrls.forEach(serverUrl => {
const version = getVersion(serverUrl.url);
expect(version).toEqual(serverUrl.version);
});
});

test('should return the version from the paths', () => {
paths.forEach(path => {
const version = getVersion(path.path);
expect(version).toEqual(path.version);
});
});
});

0 comments on commit c1a6b30

Please sign in to comment.