Skip to content

Commit 5b10785

Browse files
authored
Merge pull request #12 from mojisdev/validate-openapi
feat: validate openapi
2 parents 2c23153 + 54e64f1 commit 5b10785

12 files changed

+2263
-211
lines changed

.github/workflows/deploy-preview.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ jobs:
4141
run: pnpm build
4242

4343
- name: validate openapi
44-
run: pnpm run validate-openapi
44+
run: pnpm run lint:openapi
4545

4646
- name: lint
4747
run: pnpm run lint

.github/workflows/deploy.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ jobs:
4040
run: pnpm build
4141

4242
- name: validate openapi
43-
run: pnpm run validate-openapi
43+
run: pnpm run lint:openapi
4444

4545
- name: lint
4646
run: pnpm run lint

.spectral.yml

+199
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
extends: [[spectral:oas, all], [spectral:asyncapi, all]]
2+
rules:
3+
operation-tags: off
4+
operation-operationId: off
5+
operation-success-response: error
6+
7+
# ----------------------------#
8+
# Mojis OAS v2.0, v3.0 rules #
9+
# ----------------------------#
10+
11+
mojis-paths-kebab-case:
12+
description: All YAML/JSON paths MUST follow kebab-case
13+
severity: warn
14+
recommended: true
15+
message: "{{property}} is not kebab-case: {{error}}"
16+
given: $.paths[*]~
17+
then:
18+
function: pattern
19+
functionOptions:
20+
match: "^\/([a-z0-9]+(-[a-z0-9]+)*)?(\/[a-z0-9]+(-[a-z0-9]+)*|\/{.+})*$" # doesn't allow /asasd{asdas}sadas pattern or not closed braces
21+
22+
mojis-path-parameters-camelCase-alphanumeric:
23+
description: Path parameters MUST follow camelCase
24+
severity: warn
25+
recommended: true
26+
message: "{{property}} path parameter is not camelCase: {{error}}"
27+
given: $..parameters[?(@.in == 'path')].name
28+
then:
29+
function: pattern
30+
functionOptions:
31+
match: "^[a-z][a-zA-Z0-9]+$"
32+
33+
mojis-definitions-camelCase-alphanumeric:
34+
description: All YAML/JSON definitions MUST follow fields-camelCase and be ASCII alphanumeric characters or `_` or `$`.
35+
severity: error
36+
recommended: true
37+
message: "{{property}} MUST follow camelCase and be ASCII alphanumeric characters or `_` or `$`."
38+
given: $.definitions[*]~
39+
then:
40+
function: pattern
41+
functionOptions:
42+
match: "/^[a-z$_]{1}[A-Z09$_]*/"
43+
44+
mojis-properties-camelCase-alphanumeric:
45+
description: All JSON Schema properties MUST follow fields-camelCase and be ASCII alphanumeric characters or `_` or `$`.
46+
severity: error
47+
recommended: true
48+
message: "{{property}} MUST follow camelCase and be ASCII alphanumeric characters or `_` or `$`."
49+
given: $.definitions..properties[*]~
50+
then:
51+
function: pattern
52+
functionOptions:
53+
match: "/^[a-z$_]{1}[A-Z09$_]*/"
54+
55+
mojis-request-GET-no-body:
56+
description: "A 'GET' request MUST NOT accept a 'body` parameter"
57+
severity: error
58+
given: $.paths..get.parameters..in
59+
then:
60+
function: pattern
61+
functionOptions:
62+
notMatch: /^body$/
63+
64+
mojis-headers-no-x-headers:
65+
description: "All 'HTTP' headers SHOULD NOT include 'X-' headers (https://tools.ietf.org/html/rfc6648)."
66+
severity: warn
67+
given: "$..parameters[?(@.in == 'header')].name"
68+
message: "HTTP headers SHOULD NOT include 'X-' prefix."
69+
recommended: true
70+
type: style
71+
then:
72+
function: pattern
73+
functionOptions:
74+
notMatch: "/^(x|X)-/"
75+
76+
mojis-headers-hyphenated-pascal-case:
77+
description: All `HTTP` headers MUST use `Hyphenated-Pascal-Case` notation
78+
severity: error
79+
given: "$..parameters[?(@.in == 'header')].name"
80+
message: "'HTTP' headers MUST follow 'Hyphenated-Pascal-Case' notation"
81+
recommended: true
82+
type: style
83+
then:
84+
function: pattern
85+
functionOptions:
86+
match: "/^([A-Z][a-z0-9]-)*([A-Z][a-z0-9])+/"
87+
88+
# ----------------------#
89+
# Mojis OAS v2.0 rules #
90+
# ----------------------#
91+
92+
mojis-oas2-protocol-https-only:
93+
description: ALL requests MUST go through `https` protocol only
94+
formats:
95+
- oas2
96+
recommended: true
97+
severity: error
98+
type: style
99+
message: Schemes MUST be https and no other value is allowed.
100+
given: $
101+
then:
102+
field: schemes
103+
function: schema
104+
functionOptions:
105+
schema:
106+
type: array
107+
items:
108+
type: string
109+
enum: [https]
110+
maxItems: 1
111+
112+
mojis-oas2-request-support-json:
113+
description: Every request SHOULD support `application/json` media type
114+
formats:
115+
- oas2
116+
severity: warn
117+
message: "{{description}}: {{error}}"
118+
recommended: true
119+
given: $..consumes
120+
then:
121+
function: schema
122+
functionOptions:
123+
schema:
124+
type: array
125+
contains:
126+
type: string
127+
enum:
128+
- application/json
129+
130+
mojis-oas2-example-exists-in-parameters:
131+
description: All models MUST have a valid example.
132+
severity: error
133+
recommended: true
134+
formats:
135+
- oas2
136+
message: "{{ property }} MUST have a valid example."
137+
given: "$..parameters..[?(@.in == 'body' && (@.example || @.schema.$ref))]"
138+
then:
139+
function: truthy
140+
141+
mojis-oas2-response-error-problem: # schemas and/or produces
142+
description: All error responses MUST be of media type `application/problem+json`
143+
severity: error
144+
formats:
145+
- oas2
146+
given: $.paths..responses[?( @property >= 400 && @property < 600 )]
147+
recommended: true
148+
type: style
149+
message: "Error response document MUST follow application/problem+json: {{error}}"
150+
then:
151+
field: schema.example
152+
function: schema
153+
functionOptions:
154+
schema:
155+
title: Problem Details for HTTP APIs
156+
description: Definition of [RFC7807](https://tools.ietf.org/html/rfc7807) problem detail
157+
type: object
158+
properties:
159+
type:
160+
type: string
161+
title:
162+
type: string
163+
status:
164+
type: number
165+
detail:
166+
type: string
167+
instance:
168+
type: string
169+
required:
170+
- title
171+
- detail
172+
173+
# ----------------------#
174+
# Mojis OAS v3.0 rules #
175+
# ----------------------#
176+
177+
mojis-oas3-request-support-json:
178+
description: Every request MUST support `application/json` media type
179+
formats:
180+
- oas3
181+
recommended: true
182+
severity: error
183+
message: "{{description}}: {{error}}"
184+
given: $.paths.[*].requestBody.content[?(@property.indexOf('json') === -1)]^
185+
then:
186+
function: falsy
187+
188+
mojis-oas3-protocol-https-only:
189+
description: ALL requests MUST go through `https` protocol only
190+
formats:
191+
- oas3
192+
recommended: true
193+
severity: error
194+
message: Servers MUST be https and no other protocol is allowed.
195+
given: $.servers..url
196+
then:
197+
function: pattern
198+
functionOptions:
199+
match: "/^https:/"

package.json

+4
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66
"scripts": {
77
"dev": "wrangler dev",
88
"build": "wrangler deploy --dry-run --outdir=dist",
9+
"build:openapi": "tsx ./scripts/build-openapi",
910
"deploy": "wrangler deploy",
1011
"test": "pnpm vitest --run",
1112
"test:watch": "pnpm vitest",
1213
"lint": "eslint .",
14+
"lint:openapi": "pnpm run build:openapi && spectral lint ./node_modules/.openapi/openapi.json",
1315
"typecheck": "tsc --noEmit"
1416
},
1517
"dependencies": {
@@ -23,8 +25,10 @@
2325
"@cloudflare/vitest-pool-workers": "^0.7.4",
2426
"@cloudflare/workers-types": "^4.20250224.0",
2527
"@luxass/eslint-config": "^4.15.0",
28+
"@stoplight/spectral-cli": "^6.14.2",
2629
"eslint": "^9.21.0",
2730
"eslint-plugin-format": "^1.0.1",
31+
"tsx": "^4.19.3",
2832
"typescript": "^5.8.2",
2933
"vitest": "^3.0.7",
3034
"wrangler": "^3.111.0"

0 commit comments

Comments
 (0)