Validate API OpenAPI 3 spec (#6519)

* Add @stoplight/spectral to dev dependencies for validation

* Add yarn api-docs:lint command

* Fix linting errors

* Lower severity level

* Add ibm-openapi-validator as linter, as it inspects slightly differently
This commit is contained in:
rhymes 2020-03-09 16:40:05 +01:00 committed by GitHub
parent b43df4aff7
commit 4c6b901b7a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 1221 additions and 120 deletions

100
.validaterc Normal file
View file

@ -0,0 +1,100 @@
{
"shared": {
"operations": {
"no_operation_id": "warning",
"operation_id_case_convention": [
"warning",
"lower_camel_case"
],
"no_summary": "warning",
"no_array_responses": "off",
"parameter_order": "warning",
"unused_tag": "warning",
"operation_id_naming_convention": "off"
},
"pagination": {
"pagination_style": "warning"
},
"parameters": {
"no_parameter_description": "error",
"param_name_case_convention": [
"error",
"lower_snake_case"
],
"invalid_type_format_pair": "error",
"content_type_parameter": "error",
"accept_type_parameter": "error",
"authorization_parameter": "warning",
"required_param_has_default": "warning"
},
"paths": {
"missing_path_parameter": "error",
"duplicate_path_parameter": "warning",
"snake_case_only": "off",
"paths_case_convention": [
"error",
"lower_snake_case"
]
},
"responses": {
"inline_response_schema": "warning"
},
"security_definitions": {
"unused_security_schemes": "warning",
"unused_security_scopes": "warning"
},
"security": {
"invalid_non_empty_security_array": "error"
},
"schemas": {
"invalid_type_format_pair": "off",
"snake_case_only": "off",
"no_schema_description": "off",
"no_property_description": "off",
"description_mentions_json": "warning",
"array_of_arrays": "warning",
"property_case_convention": [
"error",
"lower_snake_case"
],
"enum_case_convention": [
"error",
"lower_snake_case"
]
},
"walker": {
"no_empty_descriptions": "error",
"has_circular_references": "warning",
"$ref_siblings": "off",
"duplicate_sibling_description": "warning",
"incorrect_ref_pattern": "warning"
}
},
"swagger2": {
"operations": {
"no_consumes_for_put_or_post": "error",
"get_op_has_consumes": "warning",
"no_produces": "warning"
}
},
"oas3": {
"operations": {
"no_request_body_content": "error",
"no_request_body_name": "warning"
},
"parameters": {
"no_in_property": "error",
"invalid_in_property": "error",
"missing_schema_or_content": "error",
"has_schema_and_content": "error"
},
"responses": {
"no_response_codes": "error",
"no_success_response_codes": "warning",
"no_response_body": "warning"
},
"schemas": {
"json_or_param_binary_string": "warning"
}
}
}

View file

@ -1,4 +1,4 @@
openapi: "3.0.2"
openapi: "3.0.3"
info:
title: DEV API (beta)
description: |
@ -301,7 +301,7 @@ components:
`cover_image` is the main image of the article
*If the markdown contains a front matter, it will take precedence
on the equivalent params given in the JSON payload.*
on the equivalent params given in the payload.*
type: string
published:
description: |
@ -365,7 +365,7 @@ components:
`cover_image` is the main image of the article
*If the markdown contains a front matter, it will take precedence
on the equivalent params given in the JSON payload.*
on the equivalent params given in the payload.*
type: string
published:
description: |
@ -465,6 +465,9 @@ components:
description: Crossposting or published date time
type: string
format: date-time
body_markdown:
description: The body of the article in Markdown format
type: string
user:
$ref: "#/components/schemas/SharedUser"
organization:
@ -1350,7 +1353,7 @@ paths:
format: int32
example: 99
responses:
200:
"200":
description: A list of articles
content:
application/json:
@ -1404,7 +1407,7 @@ paths:
$ref: "#/components/examples/ArticleCreateOrganization"
responses:
201:
"201":
description: A newly created article
content:
application/json:
@ -1419,7 +1422,7 @@ paths:
schema:
type: string
format: url
400:
"400":
description: BadRequest
content:
application/json:
@ -1428,7 +1431,7 @@ paths:
examples:
error-bad-request:
$ref: "#/components/examples/ErrorBadRequest"
401:
"401":
description: Unauthorized
content:
application/json:
@ -1437,7 +1440,7 @@ paths:
examples:
error-unauthorized:
$ref: "#/components/examples/ErrorUnauthorized"
422:
"422":
description: Unprocessable Entity
content:
application/json:
@ -1484,7 +1487,7 @@ paths:
format: int32
example: 150589
responses:
200:
"200":
description: An article
content:
application/json:
@ -1493,7 +1496,7 @@ paths:
examples:
article-success:
$ref: "#/components/examples/ArticleShow"
404:
"404":
description: Resource not found
content:
application/json:
@ -1555,42 +1558,38 @@ paths:
article-update-organization:
$ref: "#/components/examples/ArticleCreateOrganization"
responses:
200:
"200":
description: The updated article
content:
application/json:
schema:
type: object
$ref: "#/components/schemas/ArticleShow"
examples:
article-success:
$ref: "#/components/examples/ArticleShow"
400:
"400":
description: BadRequest
content:
application/json:
schema:
type: object
$ref: "#/components/schemas/APIError"
examples:
error-bad-request:
$ref: "#/components/examples/ErrorBadRequest"
401:
"401":
description: Unauthorized
content:
application/json:
schema:
type: object
$ref: "#/components/schemas/APIError"
examples:
error-unauthorized:
$ref: "#/components/examples/ErrorUnauthorized"
422:
"422":
description: Unprocessable Entity
content:
application/json:
schema:
type: object
$ref: "#/components/schemas/APIError"
examples:
error-unprocessable-entity:
@ -1642,7 +1641,7 @@ paths:
format: int32
example: 30
responses:
200:
"200":
description: A list of published articles
content:
application/json:
@ -1694,7 +1693,7 @@ paths:
format: int32
example: 30
responses:
200:
"200":
description: A list of published articles
content:
application/json:
@ -1746,7 +1745,7 @@ paths:
format: int32
example: 30
responses:
200:
"200":
description: A list of articles
content:
application/json:
@ -1800,7 +1799,7 @@ paths:
format: int32
example: 30
responses:
200:
"200":
description: A list of articles
content:
application/json:
@ -1838,7 +1837,7 @@ paths:
format: int32
example: 270180
responses:
200:
"200":
description: A list of threads of comments
content:
application/json:
@ -1853,7 +1852,7 @@ paths:
$ref: "#/components/examples/CommentsDeleted"
comments-success-hidden:
$ref: "#/components/examples/CommentsHidden"
404:
"404":
description: Resource not found
content:
application/json:
@ -1891,7 +1890,7 @@ paths:
type: string
example: m35m
responses:
200:
"200":
description: A comment and its descendants
content:
application/json:
@ -1904,7 +1903,7 @@ paths:
$ref: "#/components/examples/CommentDeleted"
comments-success-hidden:
$ref: "#/components/examples/CommentHidden"
404:
"404":
description: Resource not found
content:
application/json:
@ -1960,7 +1959,7 @@ paths:
type: string
example: cfp
responses:
200:
"200":
description: A list of listings
content:
application/json:
@ -2009,7 +2008,7 @@ paths:
$ref: "#/components/examples/ListingCreateOrganization"
responses:
201:
"201":
description: A newly created Listing
content:
application/json:
@ -2018,7 +2017,7 @@ paths:
examples:
article-success:
$ref: "#/components/examples/Listing"
400:
"400":
description: BadRequest
content:
application/json:
@ -2027,7 +2026,7 @@ paths:
examples:
error-bad-request:
$ref: "#/components/examples/ErrorBadRequest"
401:
"401":
description: Unauthorized
content:
application/json:
@ -2036,7 +2035,7 @@ paths:
examples:
error-unauthorized:
$ref: "#/components/examples/ErrorUnauthorized"
402:
"402":
description: PaymentRequired
content:
application/json:
@ -2045,7 +2044,7 @@ paths:
examples:
error-unauthorized:
$ref: "#/components/examples/ErrorPaymentRequired"
422:
"422":
description: Unprocessable Entity
content:
application/json:
@ -2119,7 +2118,7 @@ paths:
format: int32
example: 30
responses:
200:
"200":
description: A list of listings
content:
application/json:
@ -2158,7 +2157,7 @@ paths:
format: int32
example: 1
responses:
200:
"200":
description: A listing
content:
application/json:
@ -2167,7 +2166,7 @@ paths:
examples:
article-success:
$ref: "#/components/examples/Listing"
404:
"404":
description: Resource not found
content:
application/json:
@ -2228,52 +2227,47 @@ paths:
listing-update:
$ref: "#/components/examples/ListingUpdate"
responses:
200:
"200":
description: The updated article
content:
application/json:
schema:
type: object
$ref: "#/components/schemas/ArticleShow"
examples:
article-success:
$ref: "#/components/examples/ArticleShow"
400:
"400":
description: BadRequest
content:
application/json:
schema:
type: object
$ref: "#/components/schemas/APIError"
examples:
error-bad-request:
$ref: "#/components/examples/ErrorBadRequest"
401:
"401":
description: Unauthorized
content:
application/json:
schema:
type: object
$ref: "#/components/schemas/APIError"
examples:
error-unauthorized:
$ref: "#/components/examples/ErrorUnauthorized"
402:
"402":
description: PaymentRequired
content:
application/json:
schema:
type: object
$ref: "#/components/schemas/APIError"
examples:
error-unauthorized:
$ref: "#/components/examples/ErrorPaymentRequired"
422:
"422":
description: Unprocessable Entity
content:
application/json:
schema:
type: object
$ref: "#/components/schemas/APIError"
examples:
error-unprocessable-entity:
@ -2311,7 +2305,7 @@ paths:
tags:
- webhooks
responses:
200:
"200":
description: A list of webhooks
content:
application/json:
@ -2359,42 +2353,38 @@ paths:
$ref: "#/components/examples/WebhookCreate"
responses:
201:
"201":
description: A newly created webhook
content:
application/json:
schema:
type: object
$ref: "#/components/schemas/WebhookShow"
examples:
webhook-success:
$ref: "#/components/examples/WebhookShow"
400:
"400":
description: BadRequest
content:
application/json:
schema:
type: object
$ref: "#/components/schemas/APIError"
examples:
error-bad-request:
$ref: "#/components/examples/ErrorBadRequest"
401:
"401":
description: Unauthorized
content:
application/json:
schema:
type: object
$ref: "#/components/schemas/APIError"
examples:
error-unauthorized:
$ref: "#/components/examples/ErrorUnauthorized"
422:
"422":
description: Unprocessable Entity
content:
application/json:
schema:
type: object
$ref: "#/components/schemas/APIError"
examples:
error-unprocessable-entity:
@ -2430,32 +2420,29 @@ paths:
format: int32
example: 123
responses:
200:
"200":
description: A webhook endpoint
content:
application/json:
schema:
type: object
$ref: "#/components/schemas/WebhookShow"
examples:
webhook-success:
$ref: "#/components/examples/WebhookShow"
401:
"401":
description: Unauthorized
content:
application/json:
schema:
type: object
$ref: "#/components/schemas/APIError"
examples:
article-unauthorized:
$ref: "#/components/examples/ErrorUnauthorized"
404:
"404":
description: Resource not found
content:
application/json:
schema:
type: object
$ref: "#/components/schemas/APIError"
examples:
article-not-found:
@ -2486,9 +2473,9 @@ paths:
format: int32
example: 123
responses:
204:
"204":
description: A successful deletion
401:
"401":
description: Unauthorized
content:
application/json:
@ -2497,7 +2484,7 @@ paths:
examples:
article-unauthorized:
$ref: "#/components/examples/ErrorUnauthorized"
404:
"404":
description: Resource not found
content:
application/json:

View file

@ -2,12 +2,12 @@
title: Contributing to the API Specification Docs
---
# Contributing to API spec docs
# Contributing to API specification docs
The API docs follow the
The API v0 is described with the
[OpenAPI 3 specification](https://spec.openapis.org/oas/v3.0.3).
## Where the docs are located
## Where you can find the spec file
We auto-generate the documentation from `api_v0.yml` within the `/docs`
directory. We use [ReDoc](https://github.com/Redocly/redoc) to turn the OpenAPI
@ -15,8 +15,8 @@ directory. We use [ReDoc](https://github.com/Redocly/redoc) to turn the OpenAPI
## Updating API docs
Whenever you make changes to the API docs, make sure to bump the version in
`api_v0.yml`.
Whenever you make changes to the API docs, make sure to bump the version at the
top of `api_v0.yml`, in `info.version`.
## Running and editing the docs locally
@ -27,7 +27,19 @@ yarn api-docs:serve
```
This will let you browse the auto-generated version of the doc locally, and it
will reload the doc after every modification of the spec file.
will reload the documentation after every change of the specification file.
## Linting and validation
We use [spectral](https://github.com/stoplightio/spectral) and
[ibm-openapi-validator](https://github.com/IBM/openapi-validator) to validate
the spec file. The validation is performed as a `pre-commit` hook.
You can also manually validate the document, running:
```shell
yarn api-docs:lint
```
If you have Visual Studio Code, we suggest you install the following extensions
that enable validation and navigation within the spec file:

View file

@ -9,11 +9,12 @@
"test": "test"
},
"scripts": {
"test": "jest app/javascript/ --coverage",
"test:watch": "jest app/javascript/ --watch",
"storybook": "start-storybook -p 6006 -c app/javascript/.storybook -s app/assets",
"api-docs:lint": "spectral lint -F hint -v docs/api_v0.yml && lint-openapi -e docs/api_v0.yml",
"api-docs:serve": "redoc-cli serve docs/api_v0.yml --options.pathInMiddlePanel --options.jsonSampleExpandLevel=all --options.menuToggle -t docs/api_template.hbs --watch",
"build-storybook": "build-storybook -c app/javascript/.storybook -s app/assets",
"api-docs:serve": "yarn redoc-cli serve docs/api_v0.yml --options.pathInMiddlePanel --options.jsonSampleExpandLevel=all --options.menuToggle -t docs/api_template.hbs --watch"
"storybook": "start-storybook -p 6006 -c app/javascript/.storybook -s app/assets",
"test": "jest app/javascript/ --coverage",
"test:watch": "jest app/javascript/ --watch"
},
"husky": {
"hooks": {
@ -25,6 +26,10 @@
"prettier --write",
"eslint --fix -c app/assets/javascripts/.eslintrc.js"
],
"docs/api_v0.yml": [
"spectral lint -F hint",
"lint-openapi -e"
],
"*.{js,jsx}": [
"prettier --write",
"eslint --fix"
@ -64,6 +69,7 @@
},
"homepage": "https://github.com/thepracticaldev/dev.to#readme",
"devDependencies": {
"@stoplight/spectral": "5.1.0",
"@storybook/addon-actions": "3.4.12",
"@storybook/addon-knobs": "3.4.12",
"@storybook/addon-links": "3.4.12",
@ -82,6 +88,7 @@
"eslint-plugin-react": "^7.18.3",
"faker": "^4.1.0",
"husky": "^4.2.3",
"ibm-openapi-validator": "0.24.1",
"jest": "^23.5.0",
"jest-axe": "^3.3.0",
"jest-fetch-mock": "^3.0.1",

1089
yarn.lock

File diff suppressed because it is too large Load diff