Skip to content

Commit fdee054

Browse files
1 parent ec63243 commit fdee054

9 files changed

+169
-71
lines changed

tools/spectral/ipa/__tests__/getMethodReturnsSingleResource.test.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,21 @@ testRule('xgen-IPA-104-get-method-returns-single-resource', [
9797
name: 'invalid resources',
9898
document: {
9999
paths: {
100+
'/arrayResource': {
101+
get: {
102+
responses: {
103+
200: {
104+
content: {
105+
'application/vnd.atlas.2024-08-05+json': {
106+
schema: {
107+
$ref: '#/components/schemas/PaginatedSchema',
108+
},
109+
},
110+
},
111+
},
112+
},
113+
},
114+
},
100115
'/arrayResource/{id}': {
101116
get: {
102117
responses: {
@@ -112,6 +127,21 @@ testRule('xgen-IPA-104-get-method-returns-single-resource', [
112127
},
113128
},
114129
},
130+
'/paginatedResource': {
131+
get: {
132+
responses: {
133+
200: {
134+
content: {
135+
'application/vnd.atlas.2024-08-05+json': {
136+
schema: {
137+
$ref: '#/components/schemas/PaginatedSchema',
138+
},
139+
},
140+
},
141+
},
142+
},
143+
},
144+
},
115145
'/paginatedResource/{id}': {
116146
get: {
117147
responses: {

tools/spectral/ipa/__tests__/singletonHasNoId.test.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ testRule('xgen-IPA-113-singleton-must-not-have-id', [
9292
name: 'invalid resources',
9393
document: {
9494
paths: {
95-
'/resource/{exampleId}/singleton1': {
95+
'/resource/{exampleId}/singletonOne': {
9696
get: {
9797
responses: {
9898
200: {
@@ -111,7 +111,7 @@ testRule('xgen-IPA-113-singleton-must-not-have-id', [
111111
},
112112
},
113113
},
114-
'/resource/{exampleId}/singleton2': {
114+
'/resource/{exampleId}/singletonTwo': {
115115
get: {
116116
responses: {
117117
200: {
@@ -130,7 +130,7 @@ testRule('xgen-IPA-113-singleton-must-not-have-id', [
130130
},
131131
},
132132
},
133-
'/resource/{exampleId}/singleton3': {
133+
'/resource/{exampleId}/singletonThree': {
134134
get: {
135135
responses: {
136136
200: {
@@ -164,19 +164,19 @@ testRule('xgen-IPA-113-singleton-must-not-have-id', [
164164
{
165165
code: 'xgen-IPA-113-singleton-must-not-have-id',
166166
message: 'Singleton resources must not have a user-provided or system-generated ID. http://go/ipa/113',
167-
path: ['paths', '/resource/{exampleId}/singleton1'],
167+
path: ['paths', '/resource/{exampleId}/singletonOne'],
168168
severity: DiagnosticSeverity.Warning,
169169
},
170170
{
171171
code: 'xgen-IPA-113-singleton-must-not-have-id',
172172
message: 'Singleton resources must not have a user-provided or system-generated ID. http://go/ipa/113',
173-
path: ['paths', '/resource/{exampleId}/singleton2'],
173+
path: ['paths', '/resource/{exampleId}/singletonTwo'],
174174
severity: DiagnosticSeverity.Warning,
175175
},
176176
{
177177
code: 'xgen-IPA-113-singleton-must-not-have-id',
178178
message: 'Singleton resources must not have a user-provided or system-generated ID. http://go/ipa/113',
179-
path: ['paths', '/resource/{exampleId}/singleton3'],
179+
path: ['paths', '/resource/{exampleId}/singletonThree'],
180180
severity: DiagnosticSeverity.Warning,
181181
},
182182
],
@@ -185,7 +185,7 @@ testRule('xgen-IPA-113-singleton-must-not-have-id', [
185185
name: 'invalid resources with exceptions',
186186
document: {
187187
paths: {
188-
'/resource/{exampleId}/singleton1': {
188+
'/resource/{exampleId}/singletonOne': {
189189
'x-xgen-IPA-exception': {
190190
'xgen-IPA-113-singleton-must-not-have-id': 'reason',
191191
},

tools/spectral/ipa/__tests__/utils/resourceEvaluation.test.js

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
isStandardResource,
66
} from '../../rulesets/functions/utils/resourceEvaluation';
77

8+
// Standard resources
89
const standardResourcePaths = ['/resource', '/resource/{exampleId}'];
910

1011
const nestedStandardResourcePaths = ['/resource/{exampleId}/nested', '/resource/{exampleId}/nested/{exampleId}'];
@@ -18,25 +19,27 @@ const standardResourceWithCustomPaths = [
1819

1920
const standardResourceMissingSubPath = ['/standardMissingSub'];
2021

21-
const standardResourceMissingSubPathInvalidFormat = ['/standard/nestedStandardMissingSub'];
22-
23-
const standardResourcePathsInvalidFormat = ['/resource1/resource2', '/resource1/resource2/{exampleId}'];
24-
22+
// Singleton resources
2523
const singletonResourcePaths = ['/resource/{exampleId}/singleton'];
2624

2725
const singletonResourceWithCustomPaths = [
2826
'/resource/{exampleId}/customSingleton',
2927
'/resource/{exampleId}/customSingleton:method',
3028
];
3129

30+
// Neither standard nor singleton resources
31+
const resourceMissingSubPathInvalidFormat = ['/standard/nestedStandardMissingSub'];
32+
33+
const resourcePathsInvalidFormat = ['/resourceOne/resourceTwo', '/resourceOne/resourceTwo/{exampleId}'];
34+
3235
const allPaths = standardResourcePaths.concat(
3336
nestedStandardResourcePaths,
3437
standardResourceWithCustomPaths,
3538
standardResourceMissingSubPath,
36-
standardResourceMissingSubPathInvalidFormat,
37-
standardResourcePathsInvalidFormat,
3839
singletonResourcePaths,
39-
singletonResourceWithCustomPaths
40+
singletonResourceWithCustomPaths,
41+
resourceMissingSubPathInvalidFormat,
42+
resourcePathsInvalidFormat
4043
);
4144

4245
describe('tools/spectral/ipa/rulesets/functions/utils/resourceEvaluation.js', () => {
@@ -65,12 +68,12 @@ describe('tools/spectral/ipa/rulesets/functions/utils/resourceEvaluation.js', ()
6568
{
6669
description: 'nested standard resource with missing sub-path',
6770
resourceCollectionPath: '/standard/nestedStandardMissingSub',
68-
expectedPaths: standardResourceMissingSubPathInvalidFormat,
71+
expectedPaths: resourceMissingSubPathInvalidFormat,
6972
},
7073
{
7174
description: 'standard resource with missing sub-path',
72-
resourceCollectionPath: '/resource1/resource2',
73-
expectedPaths: standardResourcePathsInvalidFormat,
75+
resourceCollectionPath: '/resourceOne/resourceTwo',
76+
expectedPaths: resourcePathsInvalidFormat,
7477
},
7578
{
7679
description: 'singleton resource',
@@ -110,8 +113,8 @@ describe('tools/spectral/ipa/rulesets/functions/utils/resourceEvaluation.js', ()
110113
},
111114
{
112115
description: 'nested standard resource with missing sub-path',
113-
resourcePaths: standardResourceMissingSubPathInvalidFormat,
114-
isStandardResource: true,
116+
resourcePaths: resourceMissingSubPathInvalidFormat,
117+
isStandardResource: false,
115118
},
116119
{
117120
description: 'nested standard resource',
@@ -130,8 +133,8 @@ describe('tools/spectral/ipa/rulesets/functions/utils/resourceEvaluation.js', ()
130133
},
131134
{
132135
description: 'invalid path format',
133-
resourcePaths: standardResourcePathsInvalidFormat,
134-
isStandardResource: true,
136+
resourcePaths: resourcePathsInvalidFormat,
137+
isStandardResource: false,
135138
},
136139
];
137140

@@ -161,12 +164,12 @@ describe('tools/spectral/ipa/rulesets/functions/utils/resourceEvaluation.js', ()
161164
},
162165
{
163166
description: 'nested standard resource with missing sub-path',
164-
resourcePaths: standardResourceMissingSubPathInvalidFormat,
167+
resourcePaths: resourceMissingSubPathInvalidFormat,
165168
isSingletonResource: false,
166169
},
167170
{
168171
description: 'invalid path format',
169-
resourcePaths: standardResourcePathsInvalidFormat,
172+
resourcePaths: resourcePathsInvalidFormat,
170173
isSingletonResource: false,
171174
},
172175
{

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isCustomMethod } from './utils/resourceEvaluation.js';
1+
import { isCustomMethodIdentifier } from './utils/resourceEvaluation.js';
22
import { hasException } from './utils/exceptions.js';
33
import { collectAdoption, collectAndReturnViolation, collectException } from './utils/collectionUtils.js';
44

@@ -11,7 +11,9 @@ export default (input, opts, { path }) => {
1111
// Extract the path key (e.g., '/a/{exampleId}:method') from the JSONPath.
1212
let pathKey = path[1];
1313

14-
if (!isCustomMethod(pathKey)) return;
14+
if (!isCustomMethodIdentifier(pathKey)) {
15+
return;
16+
}
1517

1618
if (hasException(input, RULE_NAME)) {
1719
collectException(input, RULE_NAME, path);

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getCustomMethodName, isCustomMethod } from './utils/resourceEvaluation.js';
1+
import { getCustomMethodName, isCustomMethodIdentifier } from './utils/resourceEvaluation.js';
22
import { hasException } from './utils/exceptions.js';
33
import { casing } from '@stoplight/spectral-functions';
44
import { collectAdoption, collectAndReturnViolation, collectException } from './utils/collectionUtils.js';
@@ -9,7 +9,9 @@ export default (input, opts, { path }) => {
99
// Extract the path key (e.g., '/a/{exampleId}:method') from the JSONPath.
1010
let pathKey = path[1];
1111

12-
if (!isCustomMethod(pathKey)) return;
12+
if (!isCustomMethodIdentifier(pathKey)) {
13+
return;
14+
}
1315

1416
if (hasException(input, RULE_NAME)) {
1517
collectException(input, RULE_NAME, path);

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import {
22
hasGetMethod,
3-
isSingleResource,
4-
isCustomMethod,
53
isStandardResource,
64
isSingletonResource,
75
getResourcePaths,
6+
isResourceCollectionIdentifier,
7+
isSingleResourceIdentifier,
88
} from './utils/resourceEvaluation.js';
99
import { hasException } from './utils/exceptions.js';
1010
import { collectAdoption, collectAndReturnViolation, collectException } from './utils/collectionUtils.js';
@@ -13,7 +13,7 @@ const RULE_NAME = 'xgen-IPA-104-resource-has-GET';
1313
const ERROR_MESSAGE = 'APIs must provide a get method for resources.';
1414

1515
export default (input, _, { path, documentInventory }) => {
16-
if (isSingleResource(input) || isCustomMethod(input)) {
16+
if (!isResourceCollectionIdentifier(input)) {
1717
return;
1818
}
1919

@@ -31,7 +31,7 @@ export default (input, _, { path, documentInventory }) => {
3131
return collectAndReturnViolation(path, RULE_NAME, ERROR_MESSAGE);
3232
}
3333
} else if (isStandardResource(resourcePaths)) {
34-
if (resourcePaths.length === 1) {
34+
if (resourcePaths.length === 1 || !isSingleResourceIdentifier(resourcePaths[1])) {
3535
return collectAndReturnViolation(path, RULE_NAME, ERROR_MESSAGE);
3636
}
3737
if (!hasGetMethod(oas.paths[resourcePaths[1]])) {
Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
import { isSingleResource, isCustomMethod, isSingletonResource, getResourcePaths } from './utils/resourceEvaluation.js';
1+
import {
2+
getResourceCollectionIdentifier,
3+
getResourcePaths,
4+
isResourceCollectionIdentifier,
5+
isSingleResourceIdentifier,
6+
isSingletonResource,
7+
isStandardResource,
8+
} from './utils/resourceEvaluation.js';
29
import { collectAdoption, collectAndReturnViolation, collectException } from './utils/collectionUtils.js';
310
import { getAllSuccessfulResponseSchemas } from './utils/methodUtils.js';
411
import { hasException } from './utils/exceptions.js';
@@ -12,28 +19,33 @@ const ERROR_MESSAGE_STANDARD_RESOURCE =
1219
export default (input, _, { path, documentInventory }) => {
1320
const oas = documentInventory.resolved;
1421
const resourcePath = path[1];
15-
const resourcePaths = getResourcePaths(resourcePath, Object.keys(oas.paths));
1622

17-
if (isCustomMethod(resourcePath) || (!isSingleResource(resourcePath) && !isSingletonResource(resourcePaths))) {
18-
return;
19-
}
20-
21-
const errors = [];
23+
if (
24+
(isSingleResourceIdentifier(resourcePath) &&
25+
isStandardResource(getResourcePaths(getResourceCollectionIdentifier(resourcePath), Object.keys(oas.paths)))) ||
26+
(isResourceCollectionIdentifier(resourcePath) &&
27+
isSingletonResource(getResourcePaths(resourcePath, Object.keys(oas.paths))))
28+
) {
29+
const errors = [];
2230

23-
const responseSchemas = getAllSuccessfulResponseSchemas(input);
24-
responseSchemas.forEach(({ schemaPath, schema }) => {
25-
const fullPath = path.concat(schemaPath);
26-
const responseObject = resolveObject(oas, fullPath);
31+
const responseSchemas = getAllSuccessfulResponseSchemas(input);
32+
responseSchemas.forEach(({ schemaPath, schema }) => {
33+
const fullPath = path.concat(schemaPath);
34+
const responseObject = resolveObject(oas, fullPath);
2735

28-
if (hasException(responseObject, RULE_NAME)) {
29-
collectException(responseObject, RULE_NAME, fullPath);
30-
} else if (schemaIsPaginated(schema) || schemaIsArray(schema)) {
31-
collectAndReturnViolation(fullPath, RULE_NAME, ERROR_MESSAGE_STANDARD_RESOURCE);
32-
errors.push({ path: fullPath, message: ERROR_MESSAGE_STANDARD_RESOURCE });
33-
} else {
34-
collectAdoption(fullPath, RULE_NAME);
35-
}
36-
});
36+
if (hasException(responseObject, RULE_NAME)) {
37+
collectException(responseObject, RULE_NAME, fullPath);
38+
} else if (schemaIsPaginated(schema) || schemaIsArray(schema)) {
39+
collectAndReturnViolation(fullPath, RULE_NAME, ERROR_MESSAGE_STANDARD_RESOURCE);
40+
errors.push({
41+
path: fullPath,
42+
message: ERROR_MESSAGE_STANDARD_RESOURCE,
43+
});
44+
} else {
45+
collectAdoption(fullPath, RULE_NAME);
46+
}
47+
});
3748

38-
return errors;
49+
return errors;
50+
}
3951
};

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import {
22
getResourcePaths,
33
hasGetMethod,
4-
isSingleResource,
5-
isCustomMethod,
64
isSingletonResource,
5+
isResourceCollectionIdentifier,
76
} from './utils/resourceEvaluation.js';
87
import { hasException } from './utils/exceptions.js';
98
import { getAllSuccessfulResponseSchemas } from './utils/methodUtils.js';
@@ -15,7 +14,7 @@ const ERROR_MESSAGE = 'Singleton resources must not have a user-provided or syst
1514
export default (input, opts, { path, documentInventory }) => {
1615
const resourcePath = path[1];
1716

18-
if (isCustomMethod(resourcePath) || isSingleResource(resourcePath)) {
17+
if (!isResourceCollectionIdentifier(resourcePath)) {
1918
return;
2019
}
2120

0 commit comments

Comments
 (0)