# ZRO: Rails 应用 OpenApi3 JSON 文档生成器
[![Gem Version](https://badge.fury.io/rb/zero-rails_openapi.svg)](https://badge.fury.io/rb/zero-rails_openapi)
[![Build Status](https://travis-ci.org/zhandao/zero-rails_openapi.svg?branch=master)](https://travis-ci.org/zhandao/zero-rails_openapi)
[![Maintainability](https://api.codeclimate.com/v1/badges/471fd60f6eb7b019ceed/maintainability)](https://codeclimate.com/github/zhandao/zero-rails_openapi/maintainability)
[![Test Coverage](https://api.codeclimate.com/v1/badges/471fd60f6eb7b019ceed/test_coverage)](https://codeclimate.com/github/zhandao/zero-rails_openapi/test_coverage)
[![Gitter Chat](https://badges.gitter.im/zero-rails_openapi/Lobby.svg)](https://gitter.im/zero-rails_openapi/Lobby)
一套简洁的 DSL,用于为 Rails 应用生成 OpenAPI Specification 3 (**OAS3**, 旧称「Swagger3」) 标准的 JSON 文档。
(你还可以使用 Swagger-UI 3.2.0 以上版本来可视化所生成的文档。)
## Contributing
**这里是栈刀 = ▽ =
如果你在寻找能清晰书写 OAS API 文档的 DSL 工具,俺这个还挺不错的 ~
你还可以复用其所[产出](#about-openapidocs-and-openapiroutes_index)来写一些扩展,比如参数自动校验什么的(我有写哦)。
有什么想法敬请 PR,谢过!
另外,走过路过不妨来个 star?**
另外,如果对其行为表现有任何疑惑,请先阅读测试代码,这其中已表明我的大多数考量。
可一读:[api DSL](spec/api_spec.rb) 以及 [schema Obj](spec/oas_objs/schema_obj_spec.rb)。
## Table of Contents
- [关于 OAS](#about-oas) (OpenAPI Specification)
- [安装](#installation)
- [配置](#configure)
- [DSL 介绍及用例](#usage---dsl)
- [基本的 DSL](#基本的-dsl)
- [route_base](#1-route_base-optional-if-youre-writing-dsl-in-controller)
- [doc_tag](#2-doc_tag-optional)
- [components](#3-components-optional)
- [api_dry](#4-api_dry-optional)
- [api](#5-api-required)
- [用于 `api` 和 `api_dry` 块内的 DSL(描述 API 的参数、响应等)](#dsl-methods-inside-api-and-api_drys-block)
- [this_api_is_invalid!](#1-this_api_is_invalid-its-aliases)
- [desc](#2-desc-description-for-the-current-api-and-its-inputs-parameters-and-request-body)
- [param family methods](#3-param-family-methods-oas---parameter-object)
- [request_body family methods](#4-request_body-family-methods-oas---request-body-object)
- [response family methods](#5-response-family-methods-oas---response-object)
- [callback](#6-callback-oas---callback-object)
- [Authentication and Authorization](#7-authentication-and-authorization)
- [server](#8-overriding-global-servers-by-server)
- [用于 `components` 块内的 DSL(描述可复用的组件)](#dsl-methods-inside-componentss-block-code-source)
- [执行文档生成](#run---generate-json-documentation-file)
- [使用 Swagger-UI 可视化所生成的文档](#use-swagger-uivery-beautiful-web-page-to-show-your-documentation)
- [技巧](#tricks)
- [将 DSL 写于他处,与控制器分离](#trick1---write-the-dsl-somewhere-else)
- [全局 DRY](#trick2---global-drying)
- [基于 enum 等信息自动生成参数描述](#trick3---auto-generate-description)
- [跳过或使用 DRY 时(`api_dry`)所定义的参数](#trick4---skip-or-use-parameters-define-in-api_dry)
- [基于 DB Schema 自动生成 response 的格式](#trick5---auto-generate-indexshow-actionss-response-types-based-on-db-schema)
- [定义组合的 Schema (one_of / all_of / any_of / not)](#trick6---combined-schema-one_of--all_of--any_of--not)
- [问题集](#troubleshooting)
- [有关 `OpenApi.docs` 和 `OpenApi.routes_index`](#about-openapidocs-and-openapiroutes_index)
## About OAS
有关 OAS3 的所有内容请看 [OpenAPI Specification](https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.0.md)
你也可以看这份文档做初步的了解 [swagger.io](https://swagger.io/docs/specification/basic-structure/)
**建议你应该至少了解 OAS3 的基本结构**,比如说 component(组件)—— 这能帮助你进一步减少书写文档 DSL 的代码(如果其中有很多可复用的数据结构的话)。
## Installation
选一行添加到 Gemfile:
```ruby
gem 'zero-rails_openapi'
# or
gem 'zero-rails_openapi', github: 'zhandao/zero-rails_openapi'
```
命令行执行:
$ bundle
## Configure
新建一个 initializer, 用来配置 ZRO 并定义你的文档。
这是一个简单的示例:
```ruby
# config/initializers/open_api.rb
require 'open_api'
OpenApi::Config.tap do |c|
# [REQUIRED] The output location where .json doc file will be written to.
c.file_output_path = 'public/open_api'
c.open_api_docs = {
# 对文档 `homepage` 进行定义
homepage: {
# [REQUIRED] ZRO will scan all the descendants of base_doc_classes, then generate their docs.
base_doc_classes: [Api::V1::BaseController],
# [REQUIRED] OAS Info Object: The section contains API information.
info: {
# [REQUIRED] The title of the application.
title: 'Homepage APIs',
# Description of the application.
description: 'API documentation of Rails Application.
' \
'Optional multiline or single-line Markdown-formatted description ' \
'in [CommonMark](http://spec.commonmark.org/) or `HTML`.',
# [REQUIRED] The version of the OpenAPI document
# (which is distinct from the OAS version or the API implementation version).
version: '1.0.0'
}
}
}
end
```
除了直接使用 Hash,你还可以使用 DSL 来定义文档的基本信息:
```ruby
# config/initializers/open_api.rb
require 'open_api'
OpenApi::Config.tap do |c|
c.file_output_path = 'public/open_api'
c.instance_eval do
open_api :homepage_api, base_doc_classes: [ApiDoc]
info version: '1.0.0', title: 'Homepage APIs'
end
end
```
更多更详尽的配置和文档信息定义示例: [open_api.rb](documentation/examples/open_api.rb)
所有你可以配置的项目: [config.rb](lib/open_api/config.rb)
所有你可以使用的文档信息 DSL: [config_dsl.rb](lib/open_api/config_dsl.rb)
## Usage - DSL
### 首先,`include OpenApi::DSL` 到你用来写文档的基类中,例如:
```ruby
# app/controllers/api/api_controller.rb
class ApiController < ActionController::API
include OpenApi::DSL
end
```
### DSL 使用实例
一个最简单的实例:
```ruby
class Api::ExamplesController < ApiController
api :index, 'GET list' do
query :page, Integer#, desc: 'page, greater than 1', range: { ge: 1 }, dft: 1
query :rows, Integer#, desc: 'per page', range: { ge: 1 }, default: 10
end
end
```
更多更详细的实例: [goods_doc.rb](documentation/examples/goods_doc.rb)、
[examples_controller.rb](documentation/examples/examples_controller.rb),以及
[这里](https://github.com/zhandao/zero-rails/tree/master/app/_docs/v1)。
### 基本的 DSL ([source code](lib/open_api/dsl.rb))
#### (1) `route_base` [无需调用,当且仅当你是在控制器中写文档时]
```ruby
# method signature
route_base(path)
# usage
route_base 'api/v1/examples'
```
其默认设定为 `controller_path`。
[这个技巧](#trick1---write-the-dsl-somewhere-else) 展示如何使用 `route_base` 来让你将 DSL 写在他处(与控制器分离),来简化你的控制器。
#### (2) `doc_tag` [optional]
```ruby
# method signature
doc_tag(name: nil, desc: '', external_doc_url: nil)
# usage
doc_tag name: 'ExampleTagName', desc: "ExamplesController's APIs"
```
该方法可以设置当前类中声明的 API 的 Tag
(Tag 是一个 [OpenApi Object](https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.0.md#openapi-object)节点)。
Tag 的名字默认为 controller_name,除了名字,还可以设置可选参数 desc 和 external_doc_url。
#### (3) `components` [optional]
```ruby
# method signature
components(&block)
# usage
components do
# DSL for defining components
schema :DogSchema => [ { id: Integer, name: String }, dft: { id: 1, name: 'pet' } ]
query! :UidQuery => [ :uid, String, desc: 'uid' ]
resp :BadRqResp => [ 'bad request', :json ]
end
# to use component
api :action, 'summary' do
query :doge, :DogSchema # to use a Schema component
param_ref :UidQuery # to use a Parameter component
response_ref :BadRqResp # to use a Response component
end
```
Component 用以简化你的 DSL 代码 (即通过 `*_ref` 形式的方法,来引用已定义的 Component 对象)。
每个 RefObj 都是通过 component key 来关联指定的 component。
我们建议 component key 规范为驼峰命名法,且必须是 Symbol。
#### (4) `api_dry` [optional]
顾名思义,此方法用于 DRY。
```ruby
# method signature
api_dry(action = :all, desc = '', &block)
# usage
api_dry :all, 'common response' # block ...
api_dry :index # block ...
api_dry [:index, :show] do
query! #...
end
```
如你所觉,传给该方法的块,将会 eval 到指定的 API 的**开头**。
#### (5) `api` [required]
定义指定 API (或者说是一个 controller action).
```ruby
# method signature
api(action, summary = '', http: nil, skip: [ ], use: [ ], &block)
# usage
api :index, '(SUMMARY) this api blah blah ...', # block ...
```
`use` 和 `skip`: 指定使用或者跳过在 `api_dry` 中声明的参数。
```ruby
api :show, 'summary', use: [:id] # 将会从 dry 块中声明的参数中挑出 id 这个参数用于 API :show
```
### 用于 [`api`]() 和 [`api_dry`]() 块内的 DSL
[source code](lib/open_api/dsl/api_info_obj.rb)
These following methods in the block describe the specified API action: description, valid?,
parameters, request body, responses, securities, servers.
(Here corresponds to OAS [Operation Object](https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.0.md#operationObject))
#### (1) `this_api_is_invalid!`, its aliases:
```
this_api_is_expired!
this_api_is_unused!
this_api_is_under_repair!
```
```ruby
# method signature
this_api_is_invalid!(explain = '')
# usage
this_api_is_invalid! 'this api is expired!'
```
Then `deprecated` of this API will be set to true.
#### (2) `desc`: description for the current API and its inputs (parameters and request body)
```ruby
# method signature
desc(desc, param_descs = { })
# usage
desc 'current API\'s description',
id: 'desc of the parameter :id',
email: 'desc of the parameter :email'
```
You can of course describe the input in it's DSL method (like `query! :done ...`, [this line](https://github.com/zhandao/zero-rails_openapi#dsl-usage-example)),
but that will make it long and ugly. We recommend that unite descriptions in this place.
In addition, when you want to dry the same parameters (each with a different description), it will be of great use.
#### (3) `param` family methods (OAS - [Parameter Object](https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.0.md#parameterObject))
Define the parameters for the API (action).
```
param
param_ref # for reuse component,
# it links sepcified RefObjs (by component keys) to current parameters.
header, path, query, cookie # will pass specified parameter location to `param`
header!, path!, query!, cookie! # bang method of above methods
do_* by: { parameter_definations } # batch definition parameters, such as do_path, do_query
order # order parameters by names array you passed
examples # define examples of parameters
```
**The bang method (which's name is end of a exclamation point `!`) means this param is required, so without `!` means optional.**
**THE SAME BELOW.**
```ruby
# `param_type` just is the location of parameter, like: query, path
# `schema_type` is the type of parameter, like: String, Integer (must be a constant)
# For more explanation, please click the link below ↓↓↓
# method signature
param(param_type, param_name, schema_type, is_required, schema_info = { })
# usage
param :query, :page, Integer, :req, range: { gt: 0, le: 5 }, desc: 'page'
# method signature
param_ref(component_key, *component_keys) # should pass at least 1 key
# usage
param_ref :IdPath
param_ref :IdPath, :NameQuery, :TokenHeader
### method signature
header(param_name, schema_type = nil, **schema_info)
header!(param_name, schema_type = nil, **schema_info)
query!(param_name, schema_type = nil, **schema_info)
# ...
### usage
header! 'Token', String
query! :readed, Boolean, must_be: true, default: false
# The same effect as above, but not simple
param :query, :readed, Boolean, :req, must_be: true, default: false
#
# When schema_type is a Object
# (describe by hash, key means prop's name, value means prop's schema_type)
query :good, { name: String, price: Float, spec: { size: String, weight: Integer } }, desc: 'good info'
# Or you can use `type:` to sign the schema_type, maybe this is clearer for describing object
query :good, type: { name: String, price: Float, spec: { size: String, weight: Integer } }, desc: 'good info'
#
query :good_name, type: String # It's also OK, but some superfluous
query :good_name, String # recommended
# About Combined Schema (`one_of` ..), see the link below.
# method signature
do_query(by:)
# usage
do_query by: {
search_type: String,
search_val: String,
export!: Boolean
}
# The same effect as above, but a little bit repetitive
query :search_type, String
query :search_val, String
query! :export, Boolean
# method signature
# `exp_params` (select_example_by): choose the example fields.
examples(exp_params = :all, examples_hash)
# usage
# it defines 2 examples by using parameter :id and :name
# if pass :all to `exp_params`, keys will be all the parameter's names.
examples [:id, :name], {
:right_input => [ 1, 'user'], # == { id: 1, name: 'user' }
:wrong_input => [ -1, '' ]
}
```
[This trick show you how to define combined schema (by using `one_of` ..)](#trick6---combined-schema-one-of--all-of--any-of--not)
[**>> More About `param` DSL <<**](documentation/parameter.md)
#### (4) `request_body` family methods (OAS - [Request Body Object](https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.0.md#requestBodyObject))
OpenAPI 3.0 uses the requestBody keyword to distinguish the payload from parameters.
```
request_body
body_ref # for reuse component,
# it links sepcified RefObjs (by component keys) to current body.
body, body! # alias of request_body
form, form! # define a multipart/form-data body
data # define [a] property in the form-data body
file, file! # define a File media-type body
```
Bang methods(!) means the specified media-type body is required.
```ruby
# method signature
request_body(required, media_type, data: { }, **options)
# usage
# (1) `data` contains all the attributes required by this request body.
# (2) `param_name!` means it is required, otherwise without '!' means optional.
request_body :opt, :form, data: { id!: Integer, name: { type: String, desc: 'name' } }, desc: 'form-data'
# method signature
body_ref(component_key)
# usage
body_ref :UpdateDogeBody
# method signature
body!(media_type, data: { }, **options)
# usage
body :json
# method implement
def form data:, **options
body :form, data: data, **options
end
# usage
form! data: {
name: String,
password: String,
password_confirmation: String
}
# advance usage
form data: {
:name! => { type: String, desc: 'user name' },
:password! => { type: String, pattern: /[0-9]{6,10}/, desc: 'password' },
# optional
:remarks => { type: String, desc: 'remarks' },
}, exp_params: %i[ name password ],
examples: { # ↓ ↓
:right_input => [ 'user1', '123456' ],
:wrong_input => [ 'user2', 'abc' ]
},
desc: 'for creating a user'
# method implement
def data name, type = nil, schema_info = { }
schema_info[:type] = type if type.present?
form data: { name => schema_info }
end
# usage: please look at the 4th point below
# about `file`
def file! media_type, data: { type: File }, **options
body! media_type, data: data, **options
end
```
1. `media_type`: we provide some [mapping](lib/oas_objs/media_type_obj.rb) from symbols to real media-types.
2. `schema_info`: as above (see param).
3. `exp_params` and `examples`: for the above example, the following has the same effect:
```
examples: {
:right_input => { name: 'user1', password: '123456' },
:wrong_input => { name: 'user2', password: 'abc' }
}
```
4. *[IMPORTANT]* Each request bodies you declared will **FUSION** together.
(1) Media-Types will be merged to `requestBody["content"]`
```ruby
form data: { }, desc: 'desc'
body :json, data: { }, desc: 'desc'
# will generate: "content": { "multipart/form-data": { }, "application/json": { } }
```
(2) The same media-types will fusion, but not merge:
(So that you can write `form` separately, and make `data` method possible.)
```ruby
data :param_a!, String
data :param_b, Integer
# or same as:
form data: { :param_a! => String }
form data: { :param_b => Integer }
# will generate: { "param_a": { "type": "string" }, "param_b": { "type": "integer" } } (call it X)
# therefore:
# "content": { "multipart/form-data":
# { "schema": { "type": "object", "properties": { X }, "required": [ "param_a" ] }
# }
```
#### (5) `response` family methods (OAS - [Response Object](https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.0.md#response-object))
Define the responses for the API (action).
```
response # aliases: `resp` and `error`
response_ref
```
```ruby
# method signature
response(code, desc, media_type = nil, data: { }, type: nil)
# usage
resp 200, 'json response', :json, data: { name: 'test' }
response 200, 'query result', :pdf, type: File
# same as:
response 200, 'query result', :pdf, data: File
# method signature
response_ref(code_compkey_hash)
# usage
response_ref 700 => :AResp, 800 => :BResp
```
**practice:** Automatically generate responses based on the agreed error class. [AutoGenDoc](documentation/examples/auto_gen_doc.rb#L63)
#### (6) Authentication and Authorization
First of all, please make sure that you have read one of the following documents:
[OpenApi Auth](https://swagger.io/docs/specification/authentication/)
or [securitySchemeObject](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#securitySchemeObject)
##### Define Security Scheme
Use these DSL in your initializer or `components` block:
```
security_scheme # alias `auth_scheme`
base_auth # will call `security_scheme`
bearer_auth # will call `security_scheme`
api_key # will call `security_scheme`
```
It's very simple to use (if you understand the above document)
```ruby
# method signature
security_scheme(scheme_name, other_info)
# usage
security_scheme :BasicAuth, { type: 'http', scheme: 'basic', desc: 'basic auth' }
# method signature
base_auth(scheme_name, other_info = { })
bearer_auth(scheme_name, format = 'JWT', other_info = { })
api_key(scheme_name, field:, in:, **other_info)
# usage
base_auth :BasicAuth, desc: 'basic auth' # the same effect as ↑↑↑
bearer_auth :Token
api_key :ApiKeyAuth, field: 'X-API-Key', in: 'header', desc: 'pass api key to header'
```
##### Apply Security
```
# In initializer
# Global effectiveness
global_security_require
global_security # alias
global_auth # alias
# In `api`'s block
# Only valid for the current controller
security_require
security # alias
auth # alias
need_auth # alias
```
Name is different, signature and usage is similar.
```ruby
# method signature
security_require(scheme_name, scopes: [ ])
# usage
global_auth :Token
need_auth :Token
auth :OAuth, scopes: %w[ read_example admin ]
```
#### (7) Overriding Global Servers by `server`
```ruby
# method signature
server(url, desc: '')
# usage
server 'http://localhost:3000', desc: 'local'
```
### DSL methods inside [components]()'s block ([code source](lib/open_api/dsl/components.rb))
(Here corresponds to OAS [Components Object](https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.0.md#componentsObject))
Inside `components`'s block,
you can use the same DSL as [[DSL methods inside `api` and `api_dry`'s block]](#dsl-methods-inside-api-and-api_drys-block).
But there are two differences:
(1) Each method needs to pass one more parameter `component_key`
(in the first parameter position),
this will be used as the reference name for the component.
```ruby
query! :UidQuery, :uid, String
```
This writing is feasible but not recommended,
because component's key and parameter's name seem easy to confuse.
The recommended writing is:
```ruby
query! :UidQuery => [:uid, String]
```
(2) You can use `schema` to define a Schema Component.
```ruby
# method signature
schema(component_key, type = nil, **schema_info)
# usage
schema :Dog => [ String, desc: 'dogee' ] # <= schema_type is `String`
# advance usage
schema :Dog => [
{
id!: Integer,
name: { type: String, must_be: 'name', desc: 'name' }
}, # <= this hash is schema type[1]
dft: { id: 1, name: 'pet' },
desc: 'dogee'
]
# or (unrecommended)
schema :Dog, { id!: Integer, name: String }, dft: { id: 1, name: 'pet' }, desc: 'dogee'
#
# pass a ActiveRecord class constant as `component_key`,
# it will automatically read the db schema to generate the component.
schema User # easy! And the component_key will be :User
```
[1] see: [Type](documentation/parameter.md#type-schema_type)
## Run! - Generate JSON Documentation File
Use `OpenApi.write_docs`:
```ruby
# initializer
OpenApi.write_docs generate_files: !Rails.env.production?
# or run directly in console
OpenApi.write_docs # will generate json doc files
```
Then the JSON files will be written to the directories you set. (Each API a file.)
## Use Swagger UI(very beautiful web page) to show your Documentation
Download [Swagger UI](https://github.com/swagger-api/swagger-ui) (version >= 2.3.0 support the OAS3)
to your project,
change the default JSON file path(url) in index.html.
In order to use it, you may have to enable CORS, [see](https://github.com/swagger-api/swagger-ui#cors-support)
## Tricks
### Trick1 - Write the DSL Somewhere Else
Does your documentation take too many lines?
Do you want to separate documentation from business controller to simplify both?
Very easy! Just follow
```ruby
# config/initializers/open_api.rb
# in your configuration
base_doc_classes: [ApiDoc]
# app/api_doc/api_doc.rb
require 'open_api/dsl'
class ApiDoc < Object
include OpenApi::DSL
end
# app/api_doc/v1/examples_doc.rb
class V1::ExamplesDoc < ApiDoc
route_base 'api/v1/examples'
api :index do
# ...
end
end
```
Notes: file name ends in `_doc.rb` by default, but you can change via `Config.doc_location`
(it should be file paths, defaults to `./app/**/*_doc.rb`).
### Trick2 - Global DRYing
Method `api_dry` is for DRY but its scope is limited to the current controller.
I have no idea of best practices, But you can look at this [file](documentation/examples/auto_gen_doc.rb).
The implementation of the file is: do `api_dry` when inherits the base controller inside `inherited` method.
You can use `sort` to specify the order of parameters.
### Trick3 - Auto Generate Description
```ruby
desc 'api desc',
search_type!: 'search field, allows:
'
query :search_type, String, enum: %w[name creator category price]
# or
query :search_type, String, desc!: 'search field, allows:
',
enum: %w[name creator category price]
```
Notice `!` use (`search_type!`, `desc!`), it tells ZRO to append
information that analyzed from definitions (enum, must_be ..) to description automatically.
Any one of above will generate:
> search field, allows:
1/ name
2/ creator,
3/ category
4/ price
You can also use Hash to define `enum`:
```ruby
query :view, String, desc: 'allows values
', enum!: {
'all goods (default)': :all,
'only online': :online,
'only offline': :offline,
'expensive goods': :get,
'cheap goods': :borrow,
}
```
Read this [file](documentation/examples/auto_gen_desc.rb) to learn more.
### Trick4 - Skip or Use parameters define in api_dry
Pass `skip: []` and `use: []` to `api` like following code:
```ruby
api :index, 'desc', skip: [ :Token ]
```
Look at this [file](documentation/examples/goods_doc.rb) to learn more.
### Trick5 - Auto Generate index/show Actions's Response-Types Based on DB Schema
Use method `load_schema` in `api_dry`.
See this [file](documentation/examples/auto_gen_doc.rb#L51) for uasge information.
### Trick6 - Combined Schema (one_of / all_of / any_of / not)
```ruby
query :combination, one_of: [ :GoodSchema, String, { type: Integer, desc: 'integer input' } ]
form data: {
:combination_in_form => { any_of: [ Integer, String ] }
}
schema :PetSchema => [ not: [ Integer, Boolean ] ]
```
OAS: [link1](https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/),
[link2](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#schemaObject)
## Troubleshooting
- **You wrote document of the current API, but not find in the generated json file?**
Check your routing settings.
- **Report error when require `routes.rb`?***
1. Run `rails routes`.
2. Copy the output to a file, for example `config/routes.txt`.
Ignore the file `config/routes.txt`.
3. Put `c.rails_routes_file = 'config/routes.txt'` to your ZRO config.
## About `OpenApi.docs` and `OpenApi.routes_index`
After `OpenApi.write_docs`, the above two module variables will be generated.
`OpenApi.docs`: A Hash with API names as keys, and documents of each APIs as values.
documents are instances of ActiveSupport::HashWithIndifferentAccess.
`OpenApi.routes_index`: Inverted index of controller path to API name mappings.
Like: `{ 'api/v1/examples' => :homepage_api }`
It's useful when you want to look up a document based on a controller and do something.
## Development
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
## License
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
## Code of Conduct
Everyone interacting in the Zero-OpenApi project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](CODE_OF_CONDUCT.md).