Skip to content

Commit 73684db

Browse files
CLOUDP-306573: IPA rule - plaintext repsonse has example (#628)
1 parent d9bb25d commit 73684db

File tree

6 files changed

+76372
-4
lines changed

6 files changed

+76372
-4
lines changed

.github/workflows/spectral-lint.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,5 @@ jobs:
4444
file_glob: openapi/.raw/v2.yaml
4545
spectral_ruleset: tools/spectral/.spectral.yaml #If updated, need to update in MMS too.
4646
- name: IPA validation action
47-
uses: stoplightio/spectral-action@2ad0b9302e32a77c1caccf474a9b2191a8060d83
48-
with:
49-
file_glob: v2.yaml
50-
spectral_ruleset: tools/spectral/ipa/ipa-spectral.yaml
47+
run: npx spectral lint v2.yaml --ruleset=./tools/spectral/ipa/ipa-spectral.yaml
5148

outputs/v2-dev.json

Lines changed: 76143 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import testRule from './__helpers__/testRule';
2+
import { DiagnosticSeverity } from '@stoplight/types';
3+
4+
testRule('xgen-IPA-117-plaintext-response-must-have-example', [
5+
{
6+
name: 'valid responses',
7+
document: {
8+
paths: {
9+
'/resource': {
10+
get: {
11+
responses: {
12+
200: {
13+
content: {
14+
'application/vnd.atlas.2023-01-01+csv': {
15+
example: 'test',
16+
schema: {},
17+
},
18+
'text/plain': {
19+
schema: {
20+
example: 'test',
21+
type: 'string',
22+
},
23+
},
24+
},
25+
},
26+
// Ignores non-2xx
27+
400: {
28+
content: {
29+
'text/plain': {
30+
schema: {},
31+
},
32+
},
33+
},
34+
},
35+
},
36+
},
37+
// Ignores JSON/YAML
38+
'/resourceTwo': {
39+
get: {
40+
responses: {
41+
200: {
42+
content: {
43+
'application/vnd.atlas.2024-08-05+json': {
44+
type: 'string',
45+
},
46+
'application/vnd.atlas.2024-08-05+yaml': {
47+
type: 'string',
48+
},
49+
},
50+
},
51+
},
52+
},
53+
},
54+
},
55+
},
56+
errors: [],
57+
},
58+
{
59+
name: 'invalid resources',
60+
document: {
61+
paths: {
62+
'/resource': {
63+
get: {
64+
responses: {
65+
200: {
66+
content: {
67+
'application/vnd.atlas.2022-01-01+csv': {
68+
schema: {},
69+
},
70+
'application/vnd.atlas.2023-01-01+csv': {
71+
schema: {},
72+
},
73+
'text/plain': {
74+
schema: {
75+
type: 'string',
76+
},
77+
},
78+
},
79+
},
80+
},
81+
},
82+
},
83+
},
84+
},
85+
errors: [
86+
{
87+
code: 'xgen-IPA-117-plaintext-response-must-have-example',
88+
message: 'For APIs that respond with plain text, for example CSV, API producers must provide an example.',
89+
path: ['paths', '/resource', 'get', 'responses', '200', 'content', 'application/vnd.atlas.2022-01-01+csv'],
90+
severity: DiagnosticSeverity.Warning,
91+
},
92+
{
93+
code: 'xgen-IPA-117-plaintext-response-must-have-example',
94+
message: 'For APIs that respond with plain text, for example CSV, API producers must provide an example.',
95+
path: ['paths', '/resource', 'get', 'responses', '200', 'content', 'application/vnd.atlas.2023-01-01+csv'],
96+
severity: DiagnosticSeverity.Warning,
97+
},
98+
{
99+
code: 'xgen-IPA-117-plaintext-response-must-have-example',
100+
message: 'For APIs that respond with plain text, for example CSV, API producers must provide an example.',
101+
path: ['paths', '/resource', 'get', 'responses', '200', 'content', 'text/plain'],
102+
severity: DiagnosticSeverity.Warning,
103+
},
104+
],
105+
},
106+
{
107+
name: 'invalid resources with exceptions',
108+
document: {
109+
paths: {
110+
'/resource': {
111+
get: {
112+
responses: {
113+
200: {
114+
content: {
115+
'application/vnd.atlas.2022-01-01+csv': {
116+
schema: {},
117+
'x-xgen-IPA-exception': {
118+
'xgen-IPA-117-plaintext-response-must-have-example': 'reason',
119+
},
120+
},
121+
'application/vnd.atlas.2023-01-01+csv': {
122+
schema: {},
123+
'x-xgen-IPA-exception': {
124+
'xgen-IPA-117-plaintext-response-must-have-example': 'reason',
125+
},
126+
},
127+
'text/plain': {
128+
schema: {
129+
type: 'string',
130+
},
131+
'x-xgen-IPA-exception': {
132+
'xgen-IPA-117-plaintext-response-must-have-example': 'reason',
133+
},
134+
},
135+
},
136+
},
137+
},
138+
},
139+
},
140+
},
141+
},
142+
errors: [],
143+
},
144+
]);

tools/spectral/ipa/rulesets/IPA-117.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ functions:
88
- IPA117DescriptionMustNotUseHtml
99
- IPA117DescriptionShouldNotUseTables
1010
- IPA117DescriptionShouldNotUseLinks
11+
- IPA117PlaintextResponseMustHaveExample
1112

1213
rules:
1314
xgen-IPA-117-description:
@@ -157,3 +158,20 @@ rules:
157158
- '$.components.parameters[*]'
158159
then:
159160
function: 'IPA117DescriptionShouldNotUseLinks'
161+
xgen-IPA-117-plaintext-response-must-have-example:
162+
description: |
163+
For APIs that respond with plain text, for example CSV, API producers must provide an example. Some tools are not able to generate examples for such responses
164+
165+
##### Implementation details
166+
- The rule only applies to 2xx responses
167+
- The rule ignores JSON and YAML responses (passed as `allowedTypes`)
168+
- The rule checks for the presence of the example property as a sibling to the `schema` property, or inside the `schema` object
169+
message: '{{error}} https://mdb.link/mongodb-atlas-openapi-validation#xgen-IPA-117-plaintext-response-must-have-example'
170+
severity: warn
171+
given:
172+
- '$.paths[*][get,put,post,delete,options,head,patch,trace].responses[*].content'
173+
then:
174+
field: '@key'
175+
function: 'IPA117PlaintextResponseMustHaveExample'
176+
functionOptions:
177+
allowedTypes: ['json', 'yaml']

tools/spectral/ipa/rulesets/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,16 @@ Rule checks the format of the descriptions for components:
695695
- Schema properties
696696
The rule validates that the description content does not include inline markdown links. The rule ignores HTML `<a>` links - this is covered by `xgen-IPA-117-description-must-not-use-html`.
697697

698+
#### xgen-IPA-117-plaintext-response-must-have-example
699+
700+
![warn](https://img.shields.io/badge/warning-yellow)
701+
For APIs that respond with plain text, for example CSV, API producers must provide an example. Some tools are not able to generate examples for such responses
702+
703+
##### Implementation details
704+
- The rule only applies to 2xx responses
705+
- The rule ignores JSON and YAML responses (passed as `allowedTypes`)
706+
- The rule checks for the presence of the example property as a sibling to the `schema` property, or inside the `schema` object
707+
698708

699709

700710
### IPA-123
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { hasException } from './utils/exceptions.js';
2+
import {
3+
collectAdoption,
4+
collectAndReturnViolation,
5+
collectException,
6+
handleInternalError,
7+
} from './utils/collectionUtils.js';
8+
import { resolveObject } from './utils/componentUtils.js';
9+
10+
const RULE_NAME = 'xgen-IPA-117-plaintext-response-must-have-example';
11+
const ERROR_MESSAGE = 'For APIs that respond with plain text, for example CSV, API producers must provide an example.';
12+
13+
/**
14+
* For APIs that respond with plain text, for example CSV, API producers must provide an example.
15+
*
16+
* @param {object} input - A response media type
17+
* @param {{allowedTypes: string[]}} opts - Allowed type suffixes, if the type ends with one of these, it is ignored
18+
* @param {object} context - The context object containing the path and documentInventory
19+
*/
20+
export default (input, { allowedTypes }, { documentInventory, path }) => {
21+
const responseCode = path[4];
22+
23+
// Ignore non-2xx responses
24+
if (!responseCode.startsWith('2')) {
25+
return;
26+
}
27+
28+
// Ignore allowed types
29+
if (allowedTypes.some((type) => input.endsWith(type))) {
30+
return;
31+
}
32+
33+
const response = resolveObject(documentInventory.resolved, path);
34+
35+
if (hasException(response, RULE_NAME)) {
36+
collectException(response, RULE_NAME, path);
37+
return;
38+
}
39+
40+
const errors = checkViolationsAndReturnErrors(response, path);
41+
if (errors.length !== 0) {
42+
return collectAndReturnViolation(path, RULE_NAME, errors);
43+
}
44+
collectAdoption(path, RULE_NAME);
45+
};
46+
47+
function checkViolationsAndReturnErrors(response, path) {
48+
try {
49+
if (response['example'] || (response['schema'] && response['schema']['example'])) {
50+
return [];
51+
}
52+
return [{ path, message: ERROR_MESSAGE }];
53+
} catch (e) {
54+
handleInternalError(RULE_NAME, path, e);
55+
}
56+
}

0 commit comments

Comments
 (0)