Skip to content

Commit fdaec38

Browse files
feat(ipa): error on unneeded exceptions IPA 005-104
1 parent 50f7c4a commit fdaec38

13 files changed

+202
-146
lines changed
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { afterEach, describe, expect, it, jest } from '@jest/globals';
2+
import { collectExceptionAdoptionViolations } from '../../rulesets/functions/utils/collectionUtils.js';
3+
import collector from '../../metrics/collector.js';
4+
5+
const TEST_ERROR_MESSAGE = 'error message';
6+
const TEST_ENTRY_TYPE = {
7+
EXCEPTION: 'exceptions',
8+
VIOLATION: 'violations',
9+
ADOPTION: 'adoptions',
10+
};
11+
12+
jest.mock('../../metrics/collector.js', () => {
13+
return {
14+
getInstance: jest.fn(),
15+
add: jest.fn(),
16+
EntryType: TEST_ENTRY_TYPE,
17+
};
18+
});
19+
20+
describe('tools/spectral/ipa/rulesets/functions/utils/collectionUtils.js', () => {
21+
describe('collectExceptionAdoptionViolations', () => {
22+
afterEach(() => {
23+
jest.clearAllMocks();
24+
});
25+
26+
it('returns errors and collects violations when there are errors', () => {
27+
const testRuleName = 'xgen-IPA-XXX-rule';
28+
const testPath = ['paths', '/resource'];
29+
const testObject = {
30+
get: {},
31+
};
32+
const testErrors = [
33+
{
34+
path: testPath,
35+
message: TEST_ERROR_MESSAGE,
36+
},
37+
];
38+
39+
const result = collectExceptionAdoptionViolations(testErrors, testRuleName, testObject, testPath);
40+
41+
expect(result).toEqual(testErrors);
42+
expect(collector.add).toHaveBeenCalledTimes(1);
43+
expect(collector.add).toHaveBeenCalledWith(TEST_ENTRY_TYPE.VIOLATION, testPath, testRuleName);
44+
});
45+
46+
it('returns errors and collects violations when there are errors - ignores exception for another rule', () => {
47+
const testRuleName = 'xgen-IPA-XXX-rule';
48+
const testPath = ['paths', '/resource'];
49+
const testObject = {
50+
get: {},
51+
'x-xgen-IPA-exception': {
52+
'xgen-IPA-XXX-some-other-rule': 'reason',
53+
},
54+
};
55+
const testErrors = [
56+
{
57+
path: testPath,
58+
message: TEST_ERROR_MESSAGE,
59+
},
60+
];
61+
62+
const result = collectExceptionAdoptionViolations(testErrors, testRuleName, testObject, testPath);
63+
64+
expect(result).toEqual(testErrors);
65+
expect(collector.add).toHaveBeenCalledTimes(1);
66+
expect(collector.add).toHaveBeenCalledWith(TEST_ENTRY_TYPE.VIOLATION, testPath, testRuleName);
67+
});
68+
69+
it('returns empty and collects adoptions when there are no errors', () => {
70+
const testRuleName = 'xgen-IPA-XXX-rule';
71+
const testPath = ['paths', '/resource'];
72+
const testObject = {
73+
get: {},
74+
};
75+
const testNoErrors = [];
76+
77+
const result = collectExceptionAdoptionViolations(testNoErrors, testRuleName, testObject, testPath);
78+
79+
expect(result).toEqual(undefined);
80+
expect(collector.add).toHaveBeenCalledTimes(1);
81+
expect(collector.add).toHaveBeenCalledWith(TEST_ENTRY_TYPE.ADOPTION, testPath, testRuleName);
82+
});
83+
84+
it('returns empty and collects exceptions when there are errors and exceptions', () => {
85+
const testRuleName = 'xgen-IPA-XXX-rule';
86+
const testPath = ['paths', '/resource'];
87+
const testObject = {
88+
get: {},
89+
'x-xgen-IPA-exception': {
90+
'xgen-IPA-XXX-rule': 'reason',
91+
},
92+
};
93+
const testErrors = [
94+
{
95+
path: testPath,
96+
message: TEST_ERROR_MESSAGE,
97+
},
98+
];
99+
100+
const result = collectExceptionAdoptionViolations(testErrors, testRuleName, testObject, testPath);
101+
102+
expect(result).toEqual(undefined);
103+
expect(collector.add).toHaveBeenCalledTimes(1);
104+
expect(collector.add).toHaveBeenCalledWith(TEST_ENTRY_TYPE.EXCEPTION, testPath, testRuleName, 'reason');
105+
});
106+
107+
it('returns error when there are exceptions and no errors', () => {
108+
const testRuleName = 'xgen-IPA-XXX-rule';
109+
const testPath = ['paths', '/resource'];
110+
const testObject = {
111+
get: {},
112+
'x-xgen-IPA-exception': {
113+
'xgen-IPA-XXX-rule': 'reason',
114+
},
115+
};
116+
117+
const result = collectExceptionAdoptionViolations([], testRuleName, testObject, testPath);
118+
119+
expect(result.length).toEqual(1);
120+
expect(Object.keys(result[0])).toEqual(['path', 'message']);
121+
expect(result[0].path).toEqual([...testPath, 'x-xgen-IPA-exception', testRuleName]);
122+
expect(result[0].message).toEqual(
123+
'This component adopts the rule and does not need an exception. Please remove the exception.'
124+
);
125+
expect(collector.add).toHaveBeenCalledTimes(1);
126+
expect(collector.add).toHaveBeenCalledWith(TEST_ENTRY_TYPE.VIOLATION, testPath, testRuleName);
127+
});
128+
});
129+
});

tools/spectral/ipa/rulesets/functions/IPA005ExceptionExtensionFormat.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { collectAdoption, collectAndReturnViolation, handleInternalError } from './utils/collectionUtils.js';
1+
import { collectAdoptionViolations, handleInternalError } from './utils/collectionUtils.js';
22

33
const RULE_NAME = 'xgen-IPA-005-exception-extension-format';
44
const ERROR_MESSAGE = 'IPA exceptions must have a valid rule name and a reason.';
@@ -7,10 +7,7 @@ const RULE_NAME_PREFIX = 'xgen-IPA-';
77
// Note: This rule does not allow exceptions
88
export default (input, _, { path }) => {
99
const errors = checkViolationsAndReturnErrors(input, path);
10-
if (errors.length !== 0) {
11-
return collectAndReturnViolation(path, RULE_NAME, errors);
12-
}
13-
collectAdoption(path, RULE_NAME);
10+
return collectAdoptionViolations(errors, RULE_NAME, path);
1411
};
1512

1613
function isValidException(ruleName, reason) {

tools/spectral/ipa/rulesets/functions/IPA102CollectionIdentifierCamelCase.js

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,4 @@
1-
import {
2-
collectAdoption,
3-
collectAndReturnViolation,
4-
collectException,
5-
handleInternalError,
6-
} from './utils/collectionUtils.js';
7-
import { hasException } from './utils/exceptions.js';
1+
import { collectExceptionAdoptionViolations, handleInternalError } from './utils/collectionUtils.js';
82
import { isPathParam } from './utils/componentUtils.js';
93
import { casing } from '@stoplight/spectral-functions';
104

@@ -22,21 +16,12 @@ export default (input, options, { path, documentInventory }) => {
2216
const oas = documentInventory.resolved;
2317
const pathKey = input;
2418

25-
// Check for exception at the path level
26-
if (hasException(oas.paths[input], RULE_NAME)) {
27-
collectException(oas.paths[input], RULE_NAME, path);
28-
return;
29-
}
30-
3119
// Extract ignored values from options
3220
const ignoredValues = options?.ignoredValues || [];
3321

3422
const violations = checkViolations(pathKey, path, ignoredValues);
35-
if (violations.length > 0) {
36-
return collectAndReturnViolation(path, RULE_NAME, violations);
37-
}
3823

39-
return collectAdoption(path, RULE_NAME);
24+
return collectExceptionAdoptionViolations(violations, RULE_NAME, oas.paths[input], path);
4025
};
4126

4227
function checkViolations(pathKey, path, ignoredValues = []) {

tools/spectral/ipa/rulesets/functions/IPA102CollectionIdentifierPattern.js

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { collectAdoption, collectAndReturnViolation, collectException } from './utils/collectionUtils.js';
2-
import { hasException } from './utils/exceptions.js';
1+
import { collectExceptionAdoptionViolations } from './utils/collectionUtils.js';
32

43
const RULE_NAME = 'xgen-IPA-102-collection-identifier-pattern';
54
const ERROR_MESSAGE =
@@ -15,20 +14,10 @@ const VALID_IDENTIFIER_PATTERN = /^[a-z][a-zA-Z0-9]*$/;
1514
*/
1615
export default (input, _, { path, documentInventory }) => {
1716
const oas = documentInventory.resolved;
18-
const pathKey = input;
1917

20-
// Check for exception at the path level
21-
if (hasException(oas.paths[input], RULE_NAME)) {
22-
collectException(oas.paths[input], RULE_NAME, path);
23-
return;
24-
}
18+
const violations = checkViolations(input, path);
2519

26-
const violations = checkViolations(pathKey, path);
27-
if (violations.length > 0) {
28-
return collectAndReturnViolation(path, RULE_NAME, violations);
29-
}
30-
31-
return collectAdoption(path, RULE_NAME);
20+
return collectExceptionAdoptionViolations(violations, RULE_NAME, oas.paths[input], path);
3221
};
3322

3423
function checkViolations(pathKey, path) {

tools/spectral/ipa/rulesets/functions/IPA102EachPathAlternatesBetweenResourceNameAndPathParam.js

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
import { isPathParam } from './utils/componentUtils.js';
2-
import { hasException } from './utils/exceptions.js';
3-
import {
4-
collectAdoption,
5-
collectAndReturnViolation,
6-
collectException,
7-
handleInternalError,
8-
} from './utils/collectionUtils.js';
2+
import { collectExceptionAdoptionViolations, handleInternalError } from './utils/collectionUtils.js';
93
import { AUTH_PREFIX, UNAUTH_PREFIX } from './utils/resourceEvaluation.js';
104

115
const RULE_NAME = 'xgen-IPA-102-path-alternate-resource-name-path-param';
@@ -41,16 +35,9 @@ export default (input, _, { path, documentInventory }) => {
4135
return;
4236
}
4337

44-
if (hasException(oas.paths[input], RULE_NAME)) {
45-
collectException(oas.paths[input], RULE_NAME, path);
46-
return;
47-
}
48-
4938
const errors = checkViolationsAndReturnErrors(suffixWithLeadingSlash, path);
50-
if (errors.length !== 0) {
51-
return collectAndReturnViolation(path, RULE_NAME, errors);
52-
}
53-
collectAdoption(path, RULE_NAME);
39+
40+
return collectExceptionAdoptionViolations(errors, RULE_NAME, oas.paths[input], path);
5441
};
5542

5643
function checkViolationsAndReturnErrors(suffixWithLeadingSlash, path) {

tools/spectral/ipa/rulesets/functions/IPA104EachResourceHasGetMethod.js

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,7 @@ import {
55
isResourceCollectionIdentifier,
66
isSingleResourceIdentifier,
77
} from './utils/resourceEvaluation.js';
8-
import { hasException } from './utils/exceptions.js';
9-
import {
10-
collectAdoption,
11-
collectAndReturnViolation,
12-
collectException,
13-
handleInternalError,
14-
} from './utils/collectionUtils.js';
8+
import { collectExceptionAdoptionViolations, handleInternalError } from './utils/collectionUtils.js';
159

1610
const RULE_NAME = 'xgen-IPA-104-resource-has-GET';
1711
const ERROR_MESSAGE = 'APIs must provide a get method for resources.';
@@ -23,16 +17,9 @@ export default (input, _, { path, documentInventory }) => {
2317

2418
const oas = documentInventory.resolved;
2519

26-
if (hasException(oas.paths[input], RULE_NAME)) {
27-
collectException(oas.paths[input], RULE_NAME, path);
28-
return;
29-
}
30-
3120
const errors = checkViolationsAndReturnErrors(oas.paths, input, path);
32-
if (errors.length !== 0) {
33-
return collectAndReturnViolation(path, RULE_NAME, errors);
34-
}
35-
collectAdoption(path, RULE_NAME);
21+
22+
return collectExceptionAdoptionViolations(errors, RULE_NAME, oas.paths[input], path);
3623
};
3724

3825
function checkViolationsAndReturnErrors(oasPaths, input, path) {

tools/spectral/ipa/rulesets/functions/IPA104GetMethodHasNoRequestBody.js

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { hasException } from './utils/exceptions.js';
2-
import { collectAdoption, collectAndReturnViolation, collectException } from './utils/collectionUtils.js';
1+
import { collectExceptionAdoptionViolations } from './utils/collectionUtils.js';
32
import {
43
getResourcePathItems,
54
isResourceCollectionIdentifier,
@@ -23,17 +22,9 @@ export default (input, _, { path, documentInventory }) => {
2322
return;
2423
}
2524

26-
if (hasException(input, RULE_NAME)) {
27-
collectException(input, RULE_NAME, path);
28-
return;
29-
}
30-
3125
const errors = checkViolationsAndReturnErrors(input, path);
3226

33-
if (errors.length !== 0) {
34-
return collectAndReturnViolation(path, RULE_NAME, errors);
35-
}
36-
collectAdoption(path, RULE_NAME);
27+
return collectExceptionAdoptionViolations(errors, RULE_NAME, input, path);
3728
};
3829

3930
function checkViolationsAndReturnErrors(getOperationObject, path) {

tools/spectral/ipa/rulesets/functions/IPA104GetMethodResponseHasNoInputFields.js

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { hasException } from './utils/exceptions.js';
2-
import { collectAdoption, collectAndReturnViolation, collectException } from './utils/collectionUtils.js';
1+
import { collectExceptionAdoptionViolations } from './utils/collectionUtils.js';
32
import {
43
getResourcePathItems,
54
isResourceCollectionIdentifier,
@@ -30,11 +29,6 @@ export default (input, _, { path, documentInventory }) => {
3029
return;
3130
}
3231

33-
if (hasException(contentPerMediaType, RULE_NAME)) {
34-
collectException(contentPerMediaType, RULE_NAME, path);
35-
return;
36-
}
37-
3832
const errors = checkForbiddenPropertyAttributesAndReturnErrors(
3933
contentPerMediaType.schema,
4034
'writeOnly',
@@ -43,8 +37,5 @@ export default (input, _, { path, documentInventory }) => {
4337
ERROR_MESSAGE
4438
);
4539

46-
if (errors.length !== 0) {
47-
return collectAndReturnViolation(path, RULE_NAME, errors);
48-
}
49-
return collectAdoption(path, RULE_NAME);
40+
return collectExceptionAdoptionViolations(errors, RULE_NAME, contentPerMediaType, path);
5041
};

tools/spectral/ipa/rulesets/functions/IPA104GetMethodReturnsResponseSuffixedObject.js

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ import {
55
isResourceCollectionIdentifier,
66
} from './utils/resourceEvaluation.js';
77
import { resolveObject } from './utils/componentUtils.js';
8-
import { hasException } from './utils/exceptions.js';
9-
import { collectAdoption, collectAndReturnViolation, collectException } from './utils/collectionUtils.js';
8+
import { collectExceptionAdoptionViolations } from './utils/collectionUtils.js';
109
import { checkSchemaRefSuffixAndReturnErrors } from './utils/validations/checkSchemaRefSuffixAndReturnErrors.js';
1110

1211
const RULE_NAME = 'xgen-IPA-104-get-method-returns-response-suffixed-object';
@@ -29,15 +28,7 @@ export default (input, _, { path, documentInventory }) => {
2928
return;
3029
}
3130

32-
if (hasException(contentPerMediaType, RULE_NAME)) {
33-
collectException(contentPerMediaType, RULE_NAME, path);
34-
return;
35-
}
36-
3731
const errors = checkSchemaRefSuffixAndReturnErrors(path, contentPerMediaType, 'Response', RULE_NAME);
3832

39-
if (errors.length !== 0) {
40-
return collectAndReturnViolation(path, RULE_NAME, errors);
41-
}
42-
collectAdoption(path, RULE_NAME);
33+
return collectExceptionAdoptionViolations(errors, RULE_NAME, contentPerMediaType, path);
4334
};

tools/spectral/ipa/rulesets/functions/IPA104GetMethodReturnsSingleResource.js

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,7 @@ import {
44
isSingleResourceIdentifier,
55
isSingletonResource,
66
} from './utils/resourceEvaluation.js';
7-
import {
8-
collectAdoption,
9-
collectAndReturnViolation,
10-
collectException,
11-
handleInternalError,
12-
} from './utils/collectionUtils.js';
13-
import { hasException } from './utils/exceptions.js';
7+
import { collectExceptionAdoptionViolations, handleInternalError } from './utils/collectionUtils.js';
148
import { schemaIsArray, schemaIsPaginated } from './utils/schemaUtils.js';
159
import { resolveObject } from './utils/componentUtils.js';
1610

@@ -40,17 +34,9 @@ export default (input, _, { path, documentInventory }) => {
4034
return;
4135
}
4236

43-
if (hasException(contentPerMediaType, RULE_NAME)) {
44-
collectException(contentPerMediaType, RULE_NAME, path);
45-
return;
46-
}
47-
4837
const errors = checkViolationsAndReturnErrors(contentPerMediaType, path, isSingleton);
4938

50-
if (errors.length !== 0) {
51-
return collectAndReturnViolation(path, RULE_NAME, errors);
52-
}
53-
return collectAdoption(path, RULE_NAME);
39+
return collectExceptionAdoptionViolations(errors, RULE_NAME, contentPerMediaType, path);
5440
};
5541

5642
function checkViolationsAndReturnErrors(contentPerMediaType, path, isSingleton) {

0 commit comments

Comments
 (0)