Skip to content

Commit 3c620f4

Browse files
committed
feat: support custom message for built-in rules
1 parent b5243a3 commit 3c620f4

File tree

12 files changed

+252
-20
lines changed

12 files changed

+252
-20
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@redocly/openapi-core": minor
3+
"@redocly/cli": minor
4+
---
5+
6+
Added the ability to override default problem messages for built-in rules.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
openapi: 3.1.0
2+
info:
3+
version: 1.0.0
4+
title: Custom messages test
5+
paths:
6+
/test:
7+
get:
8+
responses:
9+
200:
10+
content:
11+
application/json:
12+
schema:
13+
type: object
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
apis:
2+
built-in-rule-message-override:
3+
root: ./openapi.yaml
4+
rules:
5+
info-contact:
6+
message: 'API LEVEL MESSAGE' # should override teh root-level message
7+
severity: warn
8+
operation-operationId:
9+
severity: warn
10+
message: 'API LEVEL WITH ORIGINAL MSG: {{message}}' # should enhance the original message
11+
split-documentation:
12+
root: split/openapi.yaml
13+
14+
rules:
15+
info-contact:
16+
message: ROOT LEVEL MESSAGE # should be replaced with api-level message
17+
severity: error
18+
struct:
19+
message: 'ROOT LEVEL WITH ORIGINAL MSG: {{message}}' # should enhance the original message
20+
severity: error
21+
rule/operationId:
22+
subject:
23+
type: Operation
24+
message: 'Original problem: {{problems}}' # should not interfere with assertion messages
25+
severity: error
26+
assertions:
27+
required:
28+
- operationId
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`E2E lint default-message-override 1`] = `
4+
5+
validating /openapi.yaml...
6+
[1] openapi.yaml:7:5 at #/paths/~1test/get
7+
8+
Original problem: operationId is required
9+
10+
5 | paths:
11+
6 | /test:
12+
7 | get:
13+
| ^^^
14+
8 | responses:
15+
9 | 200:
16+
17+
Error was generated by the rule/operationId rule.
18+
19+
20+
[2] openapi.yaml:9:9 at #/paths/~1test/get/responses/200
21+
22+
ROOT LEVEL WITH ORIGINAL MSG: The field \`description\` must be present on this level.
23+
24+
7 | get:
25+
8 | responses:
26+
9 | 200:
27+
| ^^^
28+
10 | content:
29+
11 | application/json:
30+
31+
Error was generated by the struct rule.
32+
33+
34+
[3] openapi.yaml:2:1 at #/info/contact
35+
36+
API LEVEL MESSAGE
37+
38+
1 | openapi: 3.1.0
39+
2 | info:
40+
| ^^^^
41+
3 | version: 1.0.0
42+
4 | title: Custom messages test
43+
44+
Warning was generated by the info-contact rule.
45+
46+
47+
[4] openapi.yaml:7:5 at #/paths/~1test/get/operationId
48+
49+
API LEVEL WITH ORIGINAL MSG: Operation object should contain \`operationId\` field.
50+
51+
5 | paths:
52+
6 | /test:
53+
7 | get:
54+
| ^^^
55+
8 | responses:
56+
9 | 200:
57+
58+
Warning was generated by the operation-operationId rule.
59+
60+
61+
/openapi.yaml: validated in <test>ms
62+
63+
validating /split/openapi.yaml...
64+
[1] split/info.yaml:1:1 at #/contact
65+
66+
ROOT LEVEL MESSAGE
67+
68+
1 | version: 1.0.0
69+
| ^^^^^^^^^^^^^^
70+
2 | title: Custom messages test
71+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
72+
3 |
73+
74+
Error was generated by the info-contact rule.
75+
76+
77+
[2] split/paths/test.yaml:1:1 at #/get
78+
79+
Original problem: operationId is required
80+
81+
1 | get:
82+
| ^^^
83+
2 | responses:
84+
3 | '200':
85+
86+
Error was generated by the rule/operationId rule.
87+
88+
89+
[3] split/paths/test.yaml:3:5 at #/get/responses/200
90+
91+
ROOT LEVEL WITH ORIGINAL MSG: The field \`description\` must be present on this level.
92+
93+
1 | get:
94+
2 | responses:
95+
3 | '200':
96+
| ^^^^^
97+
4 | content:
98+
5 | application/json:
99+
100+
Error was generated by the struct rule.
101+
102+
103+
/split/openapi.yaml: validated in <test>ms
104+
105+
❌ Validation failed with 5 errors and 2 warnings.
106+
run \`redocly lint --generate-ignore-file\` to add all problems to the ignore file.
107+
108+
109+
`;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
version: 1.0.0
2+
title: Custom messages test
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
openapi: 3.1.0
2+
info:
3+
$ref: ./info.yaml
4+
paths:
5+
/test:
6+
$ref: paths/test.yaml
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
get:
2+
responses:
3+
'200':
4+
content:
5+
application/json:
6+
schema:
7+
type: object

packages/core/src/__tests__/walk.test.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
import { BaseResolver, Document } from '../resolve';
1313
import { listOf } from '../types';
1414
import { Oas3RuleSet } from '../oas-types';
15+
import { createConfig } from '../config';
1516

1617
describe('walk order', () => {
1718
it('should run visitors', async () => {
@@ -1338,6 +1339,56 @@ describe('context.report', () => {
13381339
]
13391340
`);
13401341
});
1342+
1343+
it('should report errors with custom messages', async () => {
1344+
const document = parseYamlToDocument(
1345+
outdent`
1346+
openapi: 3.0.0
1347+
info:
1348+
license: {}
1349+
paths: {}
1350+
`,
1351+
'foobar.yaml'
1352+
);
1353+
1354+
const config = await createConfig(`
1355+
rules:
1356+
info-contact:
1357+
message: "MY ERR DESCRIPTION: {{message}}"
1358+
severity: "error"
1359+
`);
1360+
1361+
const results = await lintDocument({
1362+
externalRefResolver: new BaseResolver(),
1363+
document,
1364+
config: config.styleguide,
1365+
});
1366+
1367+
expect(results).toMatchInlineSnapshot(`
1368+
[
1369+
{
1370+
"location": [
1371+
{
1372+
"pointer": "#/info/contact",
1373+
"reportOnKey": true,
1374+
"source": Source {
1375+
"absoluteRef": "foobar.yaml",
1376+
"body": "openapi: 3.0.0
1377+
info:
1378+
license: {}
1379+
paths: {}",
1380+
"mimeType": undefined,
1381+
},
1382+
},
1383+
],
1384+
"message": "MY ERR DESCRIPTION: Info object should contain \`contact\` field.",
1385+
"ruleId": "info-contact",
1386+
"severity": "error",
1387+
"suggest": [],
1388+
},
1389+
]
1390+
`);
1391+
});
13411392
});
13421393

13431394
describe('context.resolve', () => {

packages/core/src/config/rules.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,19 +39,21 @@ export function initRules(
3939
return undefined;
4040
}
4141
const severity: ProblemSeverity = ruleSettings.severity;
42-
42+
const message = ruleSettings.message;
4343
const visitors = rule(ruleSettings);
4444

4545
if (Array.isArray(visitors)) {
4646
return visitors.map((visitor: any) => ({
4747
severity,
4848
ruleId,
49+
message,
4950
visitor: visitor,
5051
}));
5152
}
5253

5354
return {
5455
severity,
56+
message,
5557
ruleId,
5658
visitor: visitors, // note: actually it is only one visitor object
5759
};

packages/core/src/config/types.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,11 @@ import type { JSONSchema } from 'json-schema-to-ts';
2626

2727
export type RuleSeverity = ProblemSeverity | 'off';
2828

29-
export type RuleSettings = { severity: RuleSeverity };
29+
export type RuleSettings = { severity: RuleSeverity; message?: string };
3030

3131
export type PreprocessorSeverity = RuleSeverity | 'on';
3232

33-
export type RuleConfig =
34-
| RuleSeverity
35-
| ({
36-
severity?: ProblemSeverity;
37-
} & Record<string, any>);
33+
export type RuleConfig = RuleSeverity | (Partial<RuleSettings> & Record<string, any>);
3834

3935
export type PreprocessorConfig =
4036
| PreprocessorSeverity

0 commit comments

Comments
 (0)