Skip to content

Commit d2ab939

Browse files
authored
#39 Add Spectral OpenAPI specification linter (#40)
1 parent f1cb1d5 commit d2ab939

File tree

6 files changed

+184
-105
lines changed

6 files changed

+184
-105
lines changed

.github/workflows/gradle.yml

+6
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ jobs:
1616
steps:
1717
- uses: actions/checkout@v3
1818

19+
- name: Lint OpenAPI Specifications
20+
uses: stoplightio/spectral-action@latest
21+
with:
22+
file_glob: '*/api/src/main/resources/static/openapi/api.yml'
23+
spectral_ruleset: config/.spectral.yaml
24+
1925
- name: Setup up JDK 20
2026
uses: actions/setup-java@v3
2127
with:

config/.spectral.yaml

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
extends:
2+
- spectral:oas
3+
- https://unpkg.com/@apisyouwonthate/style-guide@1.5.0/dist/ruleset.js
4+
- https://unpkg.com/@stoplight/spectral-owasp-ruleset/dist/ruleset.mjs
5+
rules:
6+
# Don't know how to fix this when JWT is used
7+
oas3-operation-security-defined: off
8+
9+
# TODO: https://github.com/secs-dev/itmo-dating/issues/41
10+
operation-operationId: off
11+
12+
# TODO: https://github.com/secs-dev/itmo-dating/issues/16
13+
hosts-https-only-oas3: off
14+
15+
# TODO: https://github.com/secs-dev/itmo-dating/issues/42
16+
no-numeric-ids: off
17+
owasp:api1:2023-no-numeric-ids: off
18+
19+
# TODO: https://github.com/secs-dev/itmo-dating/issues/43
20+
no-unknown-error-format: off
21+
22+
# Too hard to follow
23+
owasp:api4:2023-rate-limit-responses-429: off
24+
owasp:api4:2023-rate-limit: off
25+
26+
# Can't suppress it only for healthcheck
27+
owasp:api2:2023-read-restricted: off
28+
owasp:api8:2023-define-error-responses-401: off
29+
owasp:api8:2023-define-error-validation: off
30+
31+
# Don't want to follow
32+
api-home: off
33+
api-health: off

matchmaker/api/src/main/resources/static/openapi/api.yml

+65-54
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,19 @@ openapi: 3.0.3
22
info:
33
title: ITMO Dating Matchmaker
44
version: 0.0.1
5+
description: Service is responsible for issuing recommendations.
6+
contact:
7+
name: ITMO Dating Team
8+
url: https://github.com/secs-dev/itmo-dating/issues
59
servers:
6-
- url: /api/v1
10+
- url: /api
11+
x-internal: true
712
security:
813
- bearerAuth: [USER, ADMIN]
14+
tags:
15+
- name: Monitoring
16+
- name: Suggestions
17+
- name: Statistics
918
paths:
1019
/monitoring/healthcheck:
1120
get:
@@ -14,16 +23,18 @@ paths:
1423
description: Returns 'ok', if service is alive, else we will cry
1524
security: []
1625
responses:
17-
200:
26+
"200":
1827
description: OK
1928
content:
2029
text/html:
2130
schema:
2231
type: string
32+
pattern: ^[a-z]+$
33+
maxLength: 32
2334
example: ok
24-
500:
35+
"500":
2536
$ref: "#/components/responses/500"
26-
503:
37+
"503":
2738
$ref: "#/components/responses/503"
2839
default:
2940
$ref: "#/components/responses/Unexpected"
@@ -45,19 +56,22 @@ paths:
4556
maximum: 50
4657
example: 8
4758
responses:
48-
200:
59+
"200":
4960
description: OK
5061
content:
5162
application/json:
5263
schema:
5364
type: array
5465
items:
5566
$ref: "#/components/schemas/PersonId"
56-
401:
67+
maxItems: 50
68+
"400":
69+
$ref: "#/components/responses/400"
70+
"401":
5771
$ref: "#/components/responses/401"
58-
500:
72+
"500":
5973
$ref: "#/components/responses/500"
60-
503:
74+
"503":
6175
$ref: "#/components/responses/503"
6276
default:
6377
$ref: "#/components/responses/Unexpected"
@@ -76,25 +90,27 @@ paths:
7690
schema:
7791
$ref: "#/components/schemas/AttitudeKind"
7892
responses:
79-
204:
93+
"204":
8094
description: Attitude was taken into account
81-
401:
95+
"400":
96+
$ref: "#/components/responses/400"
97+
"401":
8298
$ref: "#/components/responses/401"
83-
404:
99+
"404":
84100
description: Person is not found
85101
content:
86102
application/json:
87103
schema:
88104
$ref: "#/components/schemas/GeneralError"
89-
409:
105+
"409":
90106
description: Attitude was already taken
91107
content:
92108
application/json:
93109
schema:
94110
$ref: "#/components/schemas/GeneralError"
95-
500:
111+
"500":
96112
$ref: "#/components/responses/500"
97-
503:
113+
"503":
98114
$ref: "#/components/responses/503"
99115
default:
100116
$ref: "#/components/responses/Unexpected"
@@ -112,19 +128,21 @@ paths:
112128
schema:
113129
$ref: "#/components/schemas/PersonId"
114130
responses:
115-
204:
131+
"204":
116132
description: Attitudes was reset
117-
401:
133+
"400":
134+
$ref: "#/components/responses/400"
135+
"401":
118136
$ref: "#/components/responses/401"
119-
404:
137+
"404":
120138
description: Person is not found
121139
content:
122140
application/json:
123141
schema:
124142
$ref: "#/components/schemas/GeneralError"
125-
500:
143+
"500":
126144
$ref: "#/components/responses/500"
127-
503:
145+
"503":
128146
$ref: "#/components/responses/503"
129147
default:
130148
$ref: "#/components/responses/Unexpected"
@@ -136,27 +154,30 @@ paths:
136154
parameters:
137155
- $ref: "#/components/parameters/PersonIdPath"
138156
responses:
139-
200:
157+
"200":
140158
description: OK
141159
content:
142160
application/json:
143161
schema:
144162
type: array
145163
items:
146164
$ref: "#/components/schemas/PersonId"
147-
401:
165+
maxItems: 10000
166+
"400":
167+
$ref: "#/components/responses/400"
168+
"401":
148169
$ref: "#/components/responses/401"
149-
403:
170+
"403":
150171
$ref: "#/components/responses/403"
151-
404:
172+
"404":
152173
description: Person is not found. Error is available only with admin rights
153174
content:
154175
application/json:
155176
schema:
156177
$ref: "#/components/schemas/GeneralError"
157-
500:
178+
"500":
158179
$ref: "#/components/responses/500"
159-
503:
180+
"503":
160181
$ref: "#/components/responses/503"
161182
default:
162183
$ref: "#/components/responses/Unexpected"
@@ -168,7 +189,7 @@ paths:
168189
security:
169190
- bearerAuth: [ADMIN]
170191
responses:
171-
200:
192+
"200":
172193
description: OK
173194
content:
174195
application/json:
@@ -184,22 +205,27 @@ paths:
184205
description: Count of people liked by the person
185206
format: int32
186207
minimum: 0
208+
maximum: 10000000
187209
skips:
188210
type: integer
189211
description: Count of people skipped by the person
190212
format: int32
191213
minimum: 0
214+
maximum: 10000000
192215
required:
193216
- personId
194217
- likes
195218
- skips
196-
401:
219+
maxItems: 10000
220+
"400":
221+
$ref: "#/components/responses/400"
222+
"401":
197223
$ref: "#/components/responses/401"
198-
403:
224+
"403":
199225
$ref: "#/components/responses/403"
200-
500:
226+
"500":
201227
$ref: "#/components/responses/500"
202-
503:
228+
"503":
203229
$ref: "#/components/responses/503"
204230
default:
205231
$ref: "#/components/responses/Unexpected"
@@ -209,6 +235,7 @@ components:
209235
type: http
210236
scheme: bearer
211237
bearerFormat: JWT
238+
description: Supports RFC8725
212239
parameters:
213240
PersonIdPath:
214241
name: person_id
@@ -222,44 +249,40 @@ components:
222249
description: A unique key of a person, autogenerated
223250
format: int64
224251
minimum: 1
225-
example: 12345678
252+
maximum: 10000000
253+
example: 1234567
226254
AttitudeKind:
227255
type: string
228256
enum:
229257
- like
230258
- skip
231-
Attitude:
232-
type: object
233-
properties:
234-
kind:
235-
$ref: "#/components/schemas/AttitudeKind"
236-
required:
237-
- verdict
238259
GeneralError:
239260
type: object
240261
properties:
241262
code:
242263
type: integer
243264
format: int32
244265
description: HTTP Status Code
266+
minimum: 100
267+
maximum: 600
245268
example: 400
246269
status:
247270
type: string
248271
description: HTTP Status Description
272+
pattern: ^[A-Za-z0-9 .,'-]+$
273+
maxLength: 64
249274
example: Bad Request
250275
message:
251276
type: string
252277
description: Detailed Message
278+
pattern: ^[A-Za-z0-9 .,'-]+$
279+
maxLength: 128
253280
example: Username must contain only latin letter
254281
required:
255282
- code
256283
- status
257284
- message
258285
responses:
259-
"200":
260-
description: OK
261-
"204":
262-
description: No Content
263286
"400":
264287
description: Invalid arguments
265288
content:
@@ -278,18 +301,6 @@ components:
278301
application/json:
279302
schema:
280303
$ref: "#/components/schemas/GeneralError"
281-
"404":
282-
description: Not found
283-
content:
284-
application/json:
285-
schema:
286-
$ref: "#/components/schemas/GeneralError"
287-
"409":
288-
description: Duplicate request causing a conflict
289-
content:
290-
application/json:
291-
schema:
292-
$ref: "#/components/schemas/GeneralError"
293304
"500":
294305
description: Internal server error
295306
content:

matchmaker/app/src/test/kotlin/ru/ifmo/se/dating/matchmaker/api/HttpMonitoringApiTest.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class HttpMonitoringApiTest : MatchmakerTestSuite() {
1616
}
1717

1818
private fun getHealthcheck(): String {
19-
val url = "http://localhost:8080/api/v1/monitoring/healthcheck"
19+
val url = "http://localhost:8080/api/monitoring/healthcheck"
2020
val response = rest.getForEntity(url, String::class.java)
2121
return response.body!!
2222
}

0 commit comments

Comments
 (0)