Skip to content

feat: validate openapi #12

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Mar 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/deploy-preview.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:
run: pnpm build

- name: validate openapi
run: pnpm run validate-openapi
run: pnpm run lint:openapi

- name: lint
run: pnpm run lint
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
run: pnpm build

- name: validate openapi
run: pnpm run validate-openapi
run: pnpm run lint:openapi

- name: lint
run: pnpm run lint
Expand Down
199 changes: 199 additions & 0 deletions .spectral.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
extends: [[spectral:oas, all], [spectral:asyncapi, all]]
rules:
operation-tags: off
operation-operationId: off
operation-success-response: error

# ----------------------------#
# Mojis OAS v2.0, v3.0 rules #
# ----------------------------#

mojis-paths-kebab-case:
description: All YAML/JSON paths MUST follow kebab-case
severity: warn
recommended: true
message: "{{property}} is not kebab-case: {{error}}"
given: $.paths[*]~
then:
function: pattern
functionOptions:
match: "^\/([a-z0-9]+(-[a-z0-9]+)*)?(\/[a-z0-9]+(-[a-z0-9]+)*|\/{.+})*$" # doesn't allow /asasd{asdas}sadas pattern or not closed braces

mojis-path-parameters-camelCase-alphanumeric:
description: Path parameters MUST follow camelCase
severity: warn
recommended: true
message: "{{property}} path parameter is not camelCase: {{error}}"
given: $..parameters[?(@.in == 'path')].name
then:
function: pattern
functionOptions:
match: "^[a-z][a-zA-Z0-9]+$"

mojis-definitions-camelCase-alphanumeric:
description: All YAML/JSON definitions MUST follow fields-camelCase and be ASCII alphanumeric characters or `_` or `$`.
severity: error
recommended: true
message: "{{property}} MUST follow camelCase and be ASCII alphanumeric characters or `_` or `$`."
given: $.definitions[*]~
then:
function: pattern
functionOptions:
match: "/^[a-z$_]{1}[A-Z09$_]*/"

mojis-properties-camelCase-alphanumeric:
description: All JSON Schema properties MUST follow fields-camelCase and be ASCII alphanumeric characters or `_` or `$`.
severity: error
recommended: true
message: "{{property}} MUST follow camelCase and be ASCII alphanumeric characters or `_` or `$`."
given: $.definitions..properties[*]~
then:
function: pattern
functionOptions:
match: "/^[a-z$_]{1}[A-Z09$_]*/"

mojis-request-GET-no-body:
description: "A 'GET' request MUST NOT accept a 'body` parameter"
severity: error
given: $.paths..get.parameters..in
then:
function: pattern
functionOptions:
notMatch: /^body$/

mojis-headers-no-x-headers:
description: "All 'HTTP' headers SHOULD NOT include 'X-' headers (https://tools.ietf.org/html/rfc6648)."
severity: warn
given: "$..parameters[?(@.in == 'header')].name"
message: "HTTP headers SHOULD NOT include 'X-' prefix."
recommended: true
type: style
then:
function: pattern
functionOptions:
notMatch: "/^(x|X)-/"

mojis-headers-hyphenated-pascal-case:
description: All `HTTP` headers MUST use `Hyphenated-Pascal-Case` notation
severity: error
given: "$..parameters[?(@.in == 'header')].name"
message: "'HTTP' headers MUST follow 'Hyphenated-Pascal-Case' notation"
recommended: true
type: style
then:
function: pattern
functionOptions:
match: "/^([A-Z][a-z0-9]-)*([A-Z][a-z0-9])+/"

# ----------------------#
# Mojis OAS v2.0 rules #
# ----------------------#

mojis-oas2-protocol-https-only:
description: ALL requests MUST go through `https` protocol only
formats:
- oas2
recommended: true
severity: error
type: style
message: Schemes MUST be https and no other value is allowed.
given: $
then:
field: schemes
function: schema
functionOptions:
schema:
type: array
items:
type: string
enum: [https]
maxItems: 1

mojis-oas2-request-support-json:
description: Every request SHOULD support `application/json` media type
formats:
- oas2
severity: warn
message: "{{description}}: {{error}}"
recommended: true
given: $..consumes
then:
function: schema
functionOptions:
schema:
type: array
contains:
type: string
enum:
- application/json

mojis-oas2-example-exists-in-parameters:
description: All models MUST have a valid example.
severity: error
recommended: true
formats:
- oas2
message: "{{ property }} MUST have a valid example."
given: "$..parameters..[?(@.in == 'body' && (@.example || @.schema.$ref))]"
then:
function: truthy

mojis-oas2-response-error-problem: # schemas and/or produces
description: All error responses MUST be of media type `application/problem+json`
severity: error
formats:
- oas2
given: $.paths..responses[?( @property >= 400 && @property < 600 )]
recommended: true
type: style
message: "Error response document MUST follow application/problem+json: {{error}}"
then:
field: schema.example
function: schema
functionOptions:
schema:
title: Problem Details for HTTP APIs
description: Definition of [RFC7807](https://tools.ietf.org/html/rfc7807) problem detail
type: object
properties:
type:
type: string
title:
type: string
status:
type: number
detail:
type: string
instance:
type: string
required:
- title
- detail

# ----------------------#
# Mojis OAS v3.0 rules #
# ----------------------#

mojis-oas3-request-support-json:
description: Every request MUST support `application/json` media type
formats:
- oas3
recommended: true
severity: error
message: "{{description}}: {{error}}"
given: $.paths.[*].requestBody.content[?(@property.indexOf('json') === -1)]^
then:
function: falsy

mojis-oas3-protocol-https-only:
description: ALL requests MUST go through `https` protocol only
formats:
- oas3
recommended: true
severity: error
message: Servers MUST be https and no other protocol is allowed.
given: $.servers..url
then:
function: pattern
functionOptions:
match: "/^https:/"
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
"scripts": {
"dev": "wrangler dev",
"build": "wrangler deploy --dry-run --outdir=dist",
"build:openapi": "tsx ./scripts/build-openapi",
"deploy": "wrangler deploy",
"test": "pnpm vitest --run",
"test:watch": "pnpm vitest",
"lint": "eslint .",
"lint:openapi": "pnpm run build:openapi && spectral lint ./node_modules/.openapi/openapi.json",
"typecheck": "tsc --noEmit"
},
"dependencies": {
Expand All @@ -23,8 +25,10 @@
"@cloudflare/vitest-pool-workers": "^0.7.4",
"@cloudflare/workers-types": "^4.20250224.0",
"@luxass/eslint-config": "^4.15.0",
"@stoplight/spectral-cli": "^6.14.2",
"eslint": "^9.21.0",
"eslint-plugin-format": "^1.0.1",
"tsx": "^4.19.3",
"typescript": "^5.8.2",
"vitest": "^3.0.7",
"wrangler": "^3.111.0"
Expand Down
Loading