Skip to content

Commit 2ab2840

Browse files
feat(ls): add lint rule for OpenAPI Parameter defined in path template (#3571)
Refs #3546
1 parent df7ab18 commit 2ab2840

File tree

8 files changed

+391
-1
lines changed

8 files changed

+391
-1
lines changed

packages/apidom-ls/src/config/codes.ts

+1
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,7 @@ enum ApilintCodes {
734734
OPENAPI2_PARAMETER_FIELD_UNIQUE_ITEMS_TYPE = 3101700,
735735
OPENAPI2_PARAMETER_FIELD_ENUM_TYPE = 3101800,
736736
OPENAPI2_PARAMETER_FIELD_MULTIPLE_OF_TYPE = 3101900,
737+
OPENAPI2_PARAMETER_FIELD_IN_PATH_TEMPLATE = 3102000,
737738

738739
OPENAPI2_ITEMS = 3110000,
739740
OPENAPI2_ITEMS_FIELD_TYPE_EQUALS = 3110100,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { DiagnosticSeverity } from 'vscode-languageserver-types';
2+
3+
import ApilintCodes from '../../../codes';
4+
import { LinterMeta } from '../../../../apidom-language-types';
5+
import { OpenAPI2, OpenAPI3 } from '../../target-specs';
6+
7+
const inPathTemplateLint: LinterMeta = {
8+
code: ApilintCodes.OPENAPI2_PARAMETER_FIELD_IN_PATH_TEMPLATE,
9+
source: 'apilint',
10+
message: 'parameter is not defined within path template',
11+
severity: DiagnosticSeverity.Error,
12+
linterFunction: 'apilintOpenAPIParameterInPathTemplate',
13+
marker: 'value',
14+
targetSpecs: [...OpenAPI2, ...OpenAPI3],
15+
};
16+
17+
export default inPathTemplateLint;

packages/apidom-ls/src/config/openapi/parameter/lint/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import minLengthTypeLint from './min-length--type';
4040
import uniqueItemsTypeLint from './unique-items--type';
4141
import enumTypeLint from './enum--type';
4242
import multipleOfTypeLint from './multiple-of--type';
43+
import inPathTemplateLint from './in-path-template';
4344

4445
const lints = [
4546
nameTypeLint,
@@ -84,6 +85,7 @@ const lints = [
8485
allowedFields2_0Lint,
8586
allowedFields3_0Lint,
8687
allowedFields3_1Lint,
88+
inPathTemplateLint,
8789
];
8890

8991
export default lints;

packages/apidom-ls/src/services/validation/linter-functions.ts

+37-1
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ import {
99
ArraySlice,
1010
ObjectElement,
1111
isArrayElement,
12+
includesClasses,
1213
} from '@swagger-api/apidom-core';
1314
import { CompletionItem } from 'vscode-languageserver-types';
14-
import { test, resolve } from 'openapi-path-templating';
15+
import { test, resolve, parse } from 'openapi-path-templating';
1516

1617
// eslint-disable-next-line import/no-cycle
1718
import {
@@ -1068,4 +1069,39 @@ export const standardLinterfunctions: FunctionItem[] = [
10681069
return true;
10691070
},
10701071
},
1072+
{
1073+
functionName: 'apilintOpenAPIParameterInPathTemplate',
1074+
function: (element: Element) => {
1075+
if (element.element === 'parameter') {
1076+
const parameterLocation = toValue((element as ObjectElement).get('in'));
1077+
1078+
if (parameterLocation !== 'path') return true;
1079+
1080+
const isInPathItemElement =
1081+
isArrayElement(element.parent) &&
1082+
includesClasses(['path-item-parameters'], element.parent);
1083+
1084+
if (!isInPathItemElement) return true;
1085+
1086+
const pathItemElement = element.parent.parent.parent;
1087+
const isPathItemPartOfPathTemplating = isStringElement(pathItemElement.meta.get('path'));
1088+
1089+
if (!isPathItemPartOfPathTemplating) return true;
1090+
1091+
const pathTemplate = toValue(pathItemElement.meta.get('path'));
1092+
const parameterName = toValue((element as ObjectElement).get('name'));
1093+
1094+
const parseResult = parse(pathTemplate);
1095+
if (!parseResult.result.success) return true;
1096+
1097+
const parts: [string, string][] = [];
1098+
parseResult.ast.translate(parts);
1099+
1100+
return parts.some(
1101+
([name, value]) => name === 'template-expression-param-name' && value === parameterName,
1102+
);
1103+
}
1104+
return true;
1105+
},
1106+
},
10711107
];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
swagger: '2.0'
2+
info:
3+
title: Foo
4+
version: 0.1.0
5+
parameters:
6+
test_id:
7+
name: test_id
8+
in: path
9+
required: true
10+
type: string
11+
schema:
12+
type: string
13+
format: uuid
14+
title: Test Id
15+
paths:
16+
/foo/{bar_id}:
17+
delete:
18+
summary: Delete bar id
19+
operationId: deleteBar
20+
responses:
21+
'200':
22+
description: Successful Response
23+
content:
24+
application/json:
25+
schema: {}
26+
parameters:
27+
- name: foo_id
28+
in: path
29+
required: true
30+
type: string
31+
schema:
32+
type: string
33+
format: uuid
34+
title: Foo Id
35+
- name: bar_id
36+
in: path
37+
required: true
38+
type: string
39+
schema:
40+
type: string
41+
format: uuid
42+
title: Foo Id
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
openapi: 3.0.0
2+
info:
3+
title: Foo
4+
version: 0.1.0
5+
components:
6+
parameters:
7+
test_id:
8+
name: test_id
9+
in: path
10+
required: true
11+
schema:
12+
type: string
13+
format: uuid
14+
title: Test Id
15+
paths:
16+
/foo/{bar_id}:
17+
delete:
18+
summary: Delete bar id
19+
operationId: deleteBar
20+
responses:
21+
'200':
22+
description: Successful Response
23+
content:
24+
application/json:
25+
schema: {}
26+
parameters:
27+
- name: foo_id
28+
in: path
29+
required: true
30+
schema:
31+
type: string
32+
format: uuid
33+
title: Foo Id
34+
- name: bar_id
35+
in: path
36+
required: true
37+
schema:
38+
type: string
39+
format: uuid
40+
title: Bar Id
41+
/subscribe:
42+
post:
43+
description: subscribes a client
44+
responses:
45+
'201':
46+
description: subscription successfully created
47+
content:
48+
application/json:
49+
schema: {}
50+
callbacks:
51+
onData:
52+
'{$request.query.callbackUrl}/data':
53+
post:
54+
requestBody:
55+
description: subscription payload
56+
content:
57+
application/json:
58+
schema:
59+
type: object
60+
properties:
61+
userData:
62+
type: string
63+
parameters:
64+
- name: baz_id
65+
in: path
66+
required: true
67+
schema:
68+
type: string
69+
format: uuid
70+
title: Baz Id
71+
responses:
72+
'202':
73+
description: "OK"
74+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
openapi: 3.1.0
2+
info:
3+
title: Foo
4+
version: 0.1.0
5+
components:
6+
parameters:
7+
test_id:
8+
name: test_id
9+
in: path
10+
required: true
11+
schema:
12+
type: string
13+
format: uuid
14+
title: Test Id
15+
webhooks:
16+
newWebhook:
17+
post:
18+
requestBody:
19+
description: new webook
20+
content:
21+
application/json:
22+
schema: {}
23+
parameters:
24+
- name: hook_id
25+
in: path
26+
required: true
27+
schema:
28+
type: string
29+
format: uuid
30+
title: Hook Id
31+
responses:
32+
"200":
33+
description: OK
34+
paths:
35+
/foo/{bar_id}:
36+
delete:
37+
summary: Delete bar id
38+
operationId: deleteBar
39+
responses:
40+
'200':
41+
description: Successful Response
42+
content:
43+
application/json:
44+
schema: {}
45+
parameters:
46+
- name: foo_id
47+
in: path
48+
required: true
49+
schema:
50+
type: string
51+
format: uuid
52+
title: Foo Id
53+
- name: bar_id
54+
in: path
55+
required: true
56+
schema:
57+
type: string
58+
format: uuid
59+
title: Bar Id
60+
/subscribe:
61+
post:
62+
description: subscribes a client
63+
responses:
64+
'201':
65+
description: subscription successfully created
66+
content:
67+
application/json:
68+
schema: {}
69+
callbacks:
70+
onData:
71+
'{$request.query.callbackUrl}/data':
72+
post:
73+
requestBody:
74+
description: subscription payload
75+
content:
76+
application/json:
77+
schema:
78+
type: object
79+
properties:
80+
userData:
81+
type: string
82+
parameters:
83+
- name: baz_id
84+
in: path
85+
required: true
86+
schema:
87+
type: string
88+
format: uuid
89+
title: Baz Id
90+
responses:
91+
'202':
92+
description: "OK"

0 commit comments

Comments
 (0)