diff --git a/README.md b/README.md
index 1e017d3..8b798c5 100644
--- a/README.md
+++ b/README.md
@@ -23,42 +23,43 @@
It may be a very useful tool if you want to write API document clearly.
I'm looking forward to your issue and PR!**
- [Need Help](https://github.com/zhandao/zero-rails_openapi/issues/14)
-
- If you have any questions, please read the test code first.
- such as [api DSL](spec/api_spec.rb) and [schema Obj](spec/oas_objs/schema_obj_spec.rb).
+ (Test cases are rich, like: [api DSL](spec/api_spec.rb) and [schema Obj](spec/oas_objs/schema_obj_spec.rb))
## Table of Contents
- [About OAS](#about-oas) (OpenAPI Specification)
- [Installation](#installation)
- [Configure](#configure)
-- [Usage - DSL](#usage---dsl)
- - [Basic DSL](#basic-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)
- - [DSL methods inside `api` and `api_dry`'s block](#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)
- - [DSL methods inside `components`'s block](#dsl-methods-inside-componentss-block-code-source)
+- [DSL Usage](#dsl-usage)
+ - [a.Basic DSL](#basic-dsl)
+ - [a.1. route_base](#1-route_base-required-if-youre-not-writing-dsl-in-controller)
+ - [a.2. doc_tag](#2-doc_tag-optional)
+ - [a.3. components](#3-components-optional)
+ - [a.4. api](#4-api-required)
+ - [a.5. api_dry](#5-api_dry-optional)
+ - [b. DSLs written inside `api` and `api_dry`'s block](#dsls-written-inside-api-and-api_drys-block)
+ - [b.1. this_api_is_invalid!](#1-this_api_is_invalid-and-its-aliases)
+ - [b.2. desc](#2-desc-description-for-the-current-api)
+ - [b.3. param family methods](#3-param-family-methods-oas---parameter-object)
+ - [b.4. request_body family methods](#4-request_body-family-methods-oas---request-body-object)
+ - [b.5. response family methods](#5-response-family-methods-oas---response-object)
+ - [b.6. callback](#6-callback-oas---callback-object)
+ - [b.7. Authentication and Authorization](#7-authentication-and-authorization)
+ - [b.8. server](#8-overriding-global-servers-by-server)
+ - [b.9. dry](#9-dry)
+ - [c. DSLs written inside `components`'s block](#dsls-written-inside-componentss-block)
+ - [d. Schema and Type](#schema-and-type)
+ - [d.1. (Schema) Type](#schema-type)
+ - [d.2. Schema](#schema)
+ - [d.3. Combined Schema](#combined-schema)
- [Run! - Generate JSON documentation file](#run---generate-json-documentation-file)
- [Use Swagger UI(very beautiful web page) to show your Documentation](#use-swagger-uivery-beautiful-web-page-to-show-your-documentation)
- [Tricks](#tricks)
- [Write DSL somewhere else](#trick1---write-the-dsl-somewhere-else)
- [Global DRYing](#trick2---global-drying)
- - [Auto generate description](#trick3---auto-generate-description)
+ - [Auto generate description form enum](#trick3---auto-generate-description-form-enum)
- [Skip or Use parameters define in `api_dry`](#trick4---skip-or-use-parameters-define-in-api_dry)
- [Atuo Generate index/show Actions's Responses Based on DB Schema](#trick5---auto-generate-indexshow-actionss-response-types-based-on-db-schema)
- - [Combined Schema (one_of / all_of / any_of / not)](#trick6---combined-schema-one_of--all_of--any_of--not)
- [Troubleshooting](#troubleshooting)
- [About `OpenApi.docs` and `OpenApi.routes_index`](#about-openapidocs-and-openapiroutes_index)
@@ -86,10 +87,6 @@
$ bundle
- Or install it yourself as:
-
- $ gem install zero-rails_openapi
-
## Configure
Create an initializer, configure ZRO and define your OpenApi documents.
@@ -97,67 +94,50 @@
This is the simplest example:
```ruby
- # config/initializers/open_api.rb
+ # in 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 = {
- # The definition of the document `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'
- }
- }
- }
+ OpenApi::Config.class_eval do
+ # Part 1: configs of this gem
+ self.file_output_path = 'public/open_api'
+
+ # Part 2: config (DSL) for generating OpenApi info
+ open_api :doc_name, base_doc_classes: [ApiDoc]
+ info version: '1.0.0', title: 'Homepage APIs'#, description: ..
+ # server 'http://localhost:3000', desc: 'Internal staging server for testing'
+ # bearer_auth :Authorization
end
```
+
+### Part 1: configs of this gem
- In addition to directly using Hash,
- you can also use DSL to define the document information:
-
- ```ruby
- # config/initializers/open_api.rb
- require 'open_api'
+ 1. `file_output_path`(required): The location where .json doc file will be output.
+ 2. `default_run_dry`: defaults to run dry blocks even if the `dry` method is not called in the (Basic) DSL block. defaults to `false`.
+ 3. `doc_location`: give regular expressions for file or folder paths. `Dir[doc_location]` will be `require` before document generates.
+ this option is only for not writing spec in controllers.
+ 4. `rails_routes_file`: give a txt's file path (which's content is the copy of `rails routes`'s output). This will speed up document generation.
+ 5. `model_base`: The parent class of models in your application. This option is for auto loading schema from database.
+ 6. `file_format`
- OpenApi::Config.tap do |c|
- c.file_output_path = 'public/open_api'
+### Part 2: config (DSL) for generating OpenApi info
- c.instance_eval do
- open_api :homepage_api, base_doc_classes: [ApiDoc]
- info version: '1.0.0', title: 'Homepage APIs'
- end
- end
- ```
+ See all the DSLs: [config_dsl.rb](lib/open_api/config_dsl.rb)
- For more detailed configuration: [open_api.rb](documentation/examples/open_api.rb)
- See all the settings options: [config.rb](lib/open_api/config.rb)
- See all the Document Definition DSL: [config_dsl.rb](lib/open_api/config_dsl.rb)
+## DSL Usage
-## Usage - DSL
+ There are two kinds of DSL for this gem: **basic** and **inside basic**.
+ 1. Basic DSLs are class methods which is for declaring your APIs, components, and spec code DRYing ...
+ 2. DSLs written inside the block of Basic DSLs, is for declaring the parameters, responses (and so on) of the specified API and component.
-### First of all, `include OpenApi::DSL` to your base class (which is for writing docs), for example:
+### First of all, `include OpenApi::DSL` in your base class (which is for writing spec):
- ```ruby
- # app/controllers/api/api_controller.rb
- class ApiController < ActionController::API
- include OpenApi::DSL
- end
- ```
+ For example:
+ ```ruby
+ # in app/controllers/api/api_controller.rb
+ class ApiController < ActionController::API
+ include OpenApi::DSL
+ end
+ ```
### DSL Usage Example
@@ -166,113 +146,115 @@
```ruby
class Api::ExamplesController < ApiController
api :index, 'GET list' do
- query :page, Integer#, desc: 'page, greater than 1', range: { ge: 1 }, dft: 1
+ query :page, Integer#, range: { ge: 1 }, default: 1
query :rows, Integer#, desc: 'per page', range: { ge: 1 }, default: 10
end
end
```
- For more example, see [goods_doc.rb](documentation/examples/goods_doc.rb), and
- [examples_controller.rb](documentation/examples/examples_controller.rb),
- or [HERE](https://github.com/zhandao/zero-rails/tree/master/app/_docs/v1).
+### Basic DSL
-### Basic DSL ([source code](lib/open_api/dsl.rb))
+ [source code](lib/open_api/dsl.rb)
-#### (1) `route_base` [optional if you're writing DSL in controller]
+#### (1) `route_base` [required if you're not writing DSL in controller]
```ruby
- # method signature
- route_base(path)
- # usage
+ # ** Method Signature
+ route_base path
+ # ** Usage
route_base 'api/v1/examples'
```
- It is optional because `route_base` defaults to `controller_path`.
- [Here's a trick](#trick1---write-the-dsl-somewhere-else): Using `route_base`, you can write the DSL somewhere else
- to simplify the current controller.
+ [Usage](#trick1---write-the-dsl-somewhere-else): write the DSL somewhere else to simplify the current controller.
#### (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"
+ # ** Method Signature
+ doc_tag name: nil, **tag_info
+ # ** Usage
+ doc_tag name: 'ExampleTagName', description: "ExamplesController's APIs"#, externalDocs: ...
```
- This method allows you to set the Tag (which is a node of [OpenApi Object](https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.0.md#openapi-object)).
+ This method allows you to set the Tag (which is a node of [OpenApi Object](https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.0.md#openapi-object))
+ of all the APIs in the class.
- Tag's name defaults to controller_name. desc and external_doc_url are optional.
+ Tag's name defaults to controller_name.
#### (3) `components` [optional]
```ruby
- # method signature
+ # ** Method Signature
components(&block)
- # usage
+ # ** Usage
components do
- # DSL for defining components
+ # (block inside) 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 ]
+ response :BadRqResp => [ 'bad request', :json ]
end
# to use component
- api :action, 'summary' do
+ api :action 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 can be used to simplify your DSL code
- (that is, to refer to the defined Component object by `*_ref` methods).
-
Each RefObj is associated with components through component key.
- We suggest that component keys should be camelized, and must be Symbol.
-#### (4) `api_dry` [optional]
+ We suggest that component keys should be camelized, and **must be Symbol**.
+
+#### (4) `api` [required]
+
+ For defining API (or we could say controller action).
+
+ ```ruby
+ # ** Method Signature
+ api action_name, summary = '', id: nil, tag: nil, http: nil, dry: Config.default_run_dry, &block
+ # ** Usage
+ api :index, '(SUMMARY) this api blah blah ...', # block ...
+ ```
+
+ Parameters explanation:
+ 1. action_name: must be the same as controller action name
+ 2. id: operationId
+ 3. http: HTTP method (like: 'GET' or 'GET|POST')
+
+#### (5) `api_dry` [optional]
This method is for DRYing.
+ The blocks passed to `api_dry` will be executed to the specified APIs which are having the actions or tags in the class.
```ruby
- # method signature
- api_dry(action = :all, desc = '', &block)
- # usage
+ # ** Method Signature
+ api_dry action_or_tags = :all, &block
+ # ** Usage
api_dry :all, 'common response' # block ...
api_dry :index # block ...
+ api_dry :TagA # block ...
+
api_dry [:index, :show] do
- query! #...
+ query #...
end
```
-
- As you think, the block will be executed to each specified API(action) **firstly**.
-
-#### (5) `api` [required]
-
- Define the specified API (or we could say controller action).
-
- ```ruby
- # method signature
- api(action, summary = '', http: nil, skip: [ ], use: [ ], &block)
- # usage
- api :index, '(SUMMARY) this api blah blah ...', # block ...
- ```
-
- `use` and `skip` options: to use or skip the parameters defined in `api_dry`.
-
+
+ And then you should call `dry` method ([detailed info]()) for executing the declared dry blocks:
```ruby
- api :show, 'summary', use: [:id] # it will only take :id from DRYed result to define the API :show
+ api :index do
+ dry
+ end
```
-### DSL methods inside [api]() and [api_dry]()'s block
+### DSLs written inside [api](#4-api-required) and [api_dry](#5-api_dry-optional)'s block
- [source code](lib/open_api/dsl/api_info_obj.rb)
+ [source code](lib/open_api/dsl/api.rb)
These following methods in the block describe the specified API action: description, valid?,
- parameters, request body, responses, securities, servers.
+ parameters, request body, responses, securities and 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:
+#### (1) `this_api_is_invalid!`, and its aliases:
```
this_api_is_expired!
this_api_is_unused!
@@ -280,243 +262,207 @@
```
```ruby
- # method signature
- this_api_is_invalid!(explain = '')
- # usage
- this_api_is_invalid! 'this api is expired!'
+ # ** Method Signature
+ this_api_is_invalid!(*)
+ # ** Usage
+ this_api_is_invalid! 'cause old version'
```
- Then `deprecated` of this API will be set to true.
+ After that, `deprecated` field of this API will be set to true.
-#### (2) `desc`: description for the current API and its inputs (parameters and request body)
+#### (2) `desc`: description for the current API
```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'
+ # ** Method Signature
+ desc string
+ # ** Usage
+ desc "current API's description"
```
- 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).
+ To define parameter for APIs.
```
- 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
+ param # 1. normal usage
+ param_ref # 2. links sepcified RefObjs (by component keys) to current parameters.
+ header, path, query, cookie # 3. passes specified parameter location (like header) to `param`
+ header!, path!, query!, cookie! # 4. bang method of above methods
+ in_* by: { parameter_definations } # 5. batch definition, such as `in_path`, `in_query`
+ examples # 6. 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.**
+ **The bang method and param_name (which's name is end of a exclamation point `!`) means this param is required. 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: {
+ # Part 1
+ # param_type: location of parameter, like: query, path [A]
+ # param_name: name of parameter, it can be Symbol or String [B]
+ # schema_type: type of parameter, like: String, Integer (must be a constant). see #schema-and-type
+ # required: :required / :req OR :optional / :opt
+ # schema: see #schema-and-type (including combined schema)
+ # ** Method Signature
+ param param_type, param_name, schema_type, required, schema = { }
+ # ** Usage
+ param :query, :page, Integer, :req, range: { gt: 0, le: 5 }, desc: 'page number'
+
+ # Part 2
+ # ** Method Signature
+ param_ref *component_key # should pass at least 1 key
+ # ** Usage
+ param_ref :IdPath#, :NameQuery, :TokenHeader
+
+ # Part 3 & 4
+ # ** Method Signature
+ header param_name, schema_type = nil, **schema
+ query! param_name, schema_type = nil, **schema
+ # ** Usage
+ header :'X-Token', String
+ query! :readed, Boolean, default: false
+ # The same effect as above, but not concise
+ param :query, :readed, Boolean, :req, default: false
+
+ # Part 5
+ # ** Method Signature
+ in_query **params_and_schema
+ # ** Usage
+ in_query(
search_type: String,
search_val: String,
- export!: Boolean
- }
- # The same effect as above, but a little bit repetitive
+ export!: { type: Boolean, desc: 'export as pdf' }
+ )
+ # The same effect as above
query :search_type, String
query :search_val, String
- query! :export, Boolean
-
-
- # method signature
- # `exp_by` (select_example_by): choose the example fields.
- examples(exp_by = :all, examples_hash)
- # usage
- # it defines 2 examples by using parameter :id and :name
- # if pass :all to `exp_by`, keys will be all the parameter's names.
+ query! :export, Boolean, desc: 'export as pdf'
+
+ # Part 6
+ # ** Method Signature
+ examples exp_params = :all, examples_hash
+ # ** Usage
+ # Suppose we have three parameters: id, name, age
+ # * normal
+ examples(
+ right_input: [ 1, 'user', 26 ],
+ wrong_input: [ 2, 'resu', 35 ]
+ )
+ # * using exp_params
examples [:id, :name], {
- :right_input => [ 1, 'user'], # == { id: 1, name: 'user' }
- :wrong_input => [ -1, '' ]
+ right_input: [ 1, 'user' ],
+ wrong_input: [ 2, 'resu' ]
}
```
- [This trick show you how to define combined schema (by using `one_of` ..)](#trick6---combined-schema-one-of--all-of--any-of--not)
+ [A] OpenAPI 3.0 distinguishes between the following parameter types based on the parameter location:
+ **header, path, query, cookie**. [more info](https://swagger.io/docs/specification/describing-parameters/)
- [**>> More About `param` DSL <<**](documentation/parameter.md)
+ [B] If `param_type` is path, for example: if the API path is `/good/:id`, you have to declare a path parameter named `id`
#### (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.
+
+ Notice: Each API has only ONE request body object. Each request body object can has multiple media types.
+ It means: call `request_body` multiple times, (schemas) will be deeply merged (let's call it [fusion](#fusion)) into a request body object.
```
- 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
+ request_body # 1. normal usage
+ body_ref # 2. it links sepcified RefObjs (by component keys) to the body.
+ body, body! # 3. alias of request_body
+ form, form! # 4. to define a multipart/form-data request_body
+ data # 5. to define [a] property in the form-data request_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
+ # Part 1
+ # ** Method Signature
+ # a. `data` contains the attributes (params, or properties) and their schemas required by the request body
+ # b. `attr_name!` means it is required, without '!' means optional
+ # c. options: desc / exp_params and examples
+ # d. available `media_type` see:
+ # https://github.com/zhandao/zero-rails_openapi/blob/master/lib/oas_objs/media_type_obj.rb#L29
+ request_body required, media_type, data: { }, desc: '', **options
+ # ** Usage
+ request_body :opt, :form, data: {
+ id!: Integer,
+ name: { type: String, desc: 'name' }
+ }, desc: 'a form-data'
+
+ # Part 2
+ # ** Method Signature
+ body_ref component_key
+ # ** Usage
+ body_ref :UpdateUserBody
+
+ # Part 3
+ # ** Method Signature
+ body! media_type, data: { }, **options
+ # ** Usage
body :json
-
- # method implement
- def form data:, **options
+ # Part 4
+ # ** method Implement
+ def form data:, **options # or `form!`
body :form, data: data, **options
end
- # usage
+ # ** Usage
form! data: {
- name: String,
- password: String,
- password_confirmation: String
+ name!: String,
+ password: { type: String, pattern: /[0-9]{6,10}/ },
}
- # 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_by: %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
+ # Part 5
+ # ** Method Signature
+ data name, type = nil, schema = { }
+ # ** Usage
+ data :password!, String, pattern: /[0-9]{6,10}/
+ ```
+
+
+ How **fusion** works:
+ 1. Difference media types will be merged into `requestBody["content"]`
+
+ ```ruby
+ form data: { }
+ body :json, data: { }
+ # will generate: "content": { "multipart/form-data": { }, "application/json": { } }
```
- 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_by` 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" ] }
- # }
- ```
+ 2. The same media-types will be deeply merged together, including their `required` array:
+ (So that you can call `form` multiple times)
+
+ ```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).
+ To define the response for APIs.
```
- response # aliases: `resp` and `error`
- response_ref
+ response # 1. aliases: `resp` and `error`
+ response_ref # 2. it links sepcified RefObjs (by component keys) to the response.
```
```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:
+ # ** Method Signature
+ response code, desc, media_type = nil, data: { }
+ # ** Usage
+ resp 200, 'success', :json, data: { name: 'test' }
response 200, 'query result', :pdf, data: File
- # method signature
- response_ref(code_compkey_hash)
- # usage
+ # ** Method Signature
+ response_ref code_and_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) Callback (OAS - [Callback Object](https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.0.md#callback-object))
[About Callbacks](https://swagger.io/docs/specification/callbacks/)
@@ -537,11 +483,11 @@
...
```
- To define callbacks, you can use `callback` method:
+ `callback` method is for defining callbacks.
```ruby
- # method signature
- callback(event_name, http_method, callback_url, &block)
- # usage
+ # ** Method Signature
+ callback event_name, http_method, callback_url, &block
+ # ** Usage
callback :myEvent, :post, 'localhost:3000/api/goods' do
query :name, String
data :token, String
@@ -564,7 +510,7 @@
##### Define Security Scheme
- Use these DSL in your initializer or `components` block:
+ Use these DSL **in your initializer config or `components` block**:
```
security_scheme # alias `auth_scheme`
base_auth # will call `security_scheme`
@@ -573,17 +519,17 @@
```
It's very simple to use (if you understand the above document)
```ruby
- # method signature
- security_scheme(scheme_name, other_info)
- # usage
+ # ** 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 ↑↑↑
+ # ** 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 above
bearer_auth :Token
api_key :ApiKeyAuth, field: 'X-API-Key', in: 'header', desc: 'pass api key to header'
```
@@ -591,98 +537,166 @@
##### Apply Security
```
- # In initializer
- # Global effectiveness
- global_security_require
- global_security # alias
- global_auth # alias
+ # Use in initializer (Global effectiveness)
+ global_security_require # alias: global_security & global_auth
- # In `api`'s block
- # Only valid for the current controller
- security_require
- security # alias
- auth # alias
- need_auth # alias
+ # Use in `api`'s block (Only valid for the current controller)
+ security_require # alias security & auth_with
```
- Name is different, signature and usage is similar.
```ruby
- # method signature
- security_require(scheme_name, scopes: [ ])
- # usage
+ # ** Method Signature
+ security_require scheme_name, scopes: [ ]
+ # ** Usage
global_auth :Token
- need_auth :Token
- auth :OAuth, scopes: %w[ read_example admin ]
+ auth_with :OAuth, scopes: %w[ read_example admin ]
```
#### (8) Overriding Global Servers by `server`
```ruby
- # method signature
- server(url, desc: '')
- # usage
+ # ** Method Signature
+ server url, desc: ''
+ # ** Usage
server 'http://localhost:3000', desc: 'local'
```
+
+#### (9) `dry`
-### DSL methods inside [components]()'s block ([code source](lib/open_api/dsl/components.rb))
+ You have to call `dry` method inside `api` block, or pass `dry: true` as parameter of `api`,
+ for executing the dry blocks you declared before. Otherwise nothing will happen.
+
+ ```ruby
+ # ** Method Signature
+ dry only: nil, skip: nil, none: false
+
+ # ** Usage
+ # In general, just:
+ dry
+ # To skip some params declared in dry blocks:
+ dry skip: [:id, :name]
+ # `only` is used to specify which parameters will be taken from dry blocks
+ dry only: [:id]
+ ```
- (Here corresponds to OAS [Components Object](https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.0.md#componentsObject))
+### DSLs written inside [components](#3-components-optional)'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.
+ you can use the same DSLs as [DSLs written inside `api` and `api_dry`'s block](#dsls-written-inside-api-and-api_drys-block).
+ But notice there are two differences:
- ```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:
+ (1) Each method needs to pass one more parameter `component_key` (as the first parameter),
+ it will be used as the reference name for the component.
```ruby
- query! :UidQuery => [:uid, String]
+ query! :UidQuery, :uid, String, desc: 'it is a component'
+ # ↑ ↑
+ # component_key param_name
+
+ # You can also use "arrow writing", it may be easier to understand
+ query! :UidQuery => [:uid, String, desc: '']
```
(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`
+ # ** Method Signature
+ schema component_key, type = nil, **schema
+ # ** Usage
+ schema :Dog => [ String, desc: 'doge' ]
# 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'
+ id!: Integer,
+ name: { type: String, desc: 'doge name' }
+ }, default: { id: 1, name: 'pet' }
]
- # or (unrecommended)
- schema :Dog, { id!: Integer, name: String }, dft: { id: 1, name: 'pet' }, desc: 'dogee'
+ # or flatten writing
+ schema :Dog, { id!: Integer, name: String }, default: { id: 1, name: 'pet' }
#
# pass a ActiveRecord class constant as `component_key`,
- # it will automatically read the db schema to generate the component.
+ # it will automatically load schema from database and then generate the component.
schema User # easy! And the component_key will be :User
```
- [1] see: [Type](documentation/parameter.md#type-schema_type)
+ To enable load schema from database, you must set [model base](#part-1-configs-of-this-gem) correctly.
+
+### Schema and Type
+
+ schema and type -- contain each other
+
+#### (Schema) Type
+
+ Support all [data types](https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.0.md#dataTypes) in OAS.
+
+ 1. String / 'binary' / 'base64' / 'uri'
+ 2. Integer / Long / 'int32' / 'int64' / Float / Double
+ 3. File (it will be converted to `{ type: 'string', format: Config.file_format }`)
+ 4. Date / DateTime
+ 5. 'boolean'
+ 6. Array / Array[\] (like: `Array[String]`, `[String]`)
+ 7. Nested Array (like: `[[[Integer]]]`)
+ 8. Object / Hash (Object with properties)
+ Example: `{ id!: Integer, name: String }`
+ 9. Nested Hash: `{ id!: Integer, name: { first: String, last: String } }`
+ 10. Nested Array[Nested Hash]: `[[{ id!: Integer, name: { first: String, last: String } }]]`
+ 11. Symbol Value: it will generate a Schema Reference Object link to the component correspond to ComponentKey, like: :IdPath, :NameQuery
+
+ **Notice** that Symbol is not allowed in all cases except 11.
+
+#### Schema
+
+ [OAS Schema Object](https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.0.md#schemaObject)
+ and [source code](https://github.com/zhandao/zero-rails_openapi/blob/master/lib/oas_objs/schema_obj.rb)
+
+ Schema (Hash) is for defining properties of parameters, responses and request bodies.
+
+ The following property keys will be process slightly:
+ 1. desc / description / d
+ 2. enum / in / values / allowable_values
+ should be Array or Range
+ 3. range: allow value in this continuous range
+ should be Range or like `{ gt: 0, le: 5 }`
+ 4. length / size / lth
+ should be an Integer, Integer Array, Integer Range,
+ or the following format Symbol: `:gt_`, `:ge_`, `:lt_`, `:le_` (:ge_5 means "greater than or equal 5"; :lt_9 means "lower than 9")
+ 5. pattern / regxp
+ 6. additional_properties / add_prop / values_type
+ 7. example
+ 8. examples
+ 9. format
+ 10. default: default value
+ 11. type
+
+ The other keys will be directly merged. Such as:
+ 1. `title: 'Property Title'`
+ 2. `myCustomKey: 'Value'`
+
+#### Combined Schema
+
+ Very easy to use:
+ ```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)
## 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
+ OpenApi.write_docs# if: !Rails.env.production?
```
+ `if` option is used to control whether a JSON document is generated or not.
+
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
@@ -735,31 +749,20 @@
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).
+ I have no idea of best practices, But you can look at this [file](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
+### Trick3 - Auto Generate Description from Enum
+ Just use `enum!`:
```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]
+ query :search_type, String, desc: 'search field, allows:
', enum!: %w[name creator category price]
+ # it will generate:
+ "search field, allows:
1/ name
2/ creator,
3/ category
4/ 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`:
+ Or Hash `enum!`:
```ruby
query :view, String, desc: 'allows values
', enum!: {
'all goods (default)': :all,
@@ -769,37 +772,6 @@
'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
@@ -826,8 +798,6 @@
## Development
- TODO ..
-
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).
@@ -838,4 +808,4 @@
## 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).
+ Everyone interacting in the Zero-RailsOpenApi project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](CODE_OF_CONDUCT.md).
diff --git a/README_zh.md b/README_zh.md
index 4b15cc3..d9114df 100644
--- a/README_zh.md
+++ b/README_zh.md
@@ -359,11 +359,11 @@
# method signature
- # `exp_by` (select_example_by): choose the example fields.
- examples(exp_by = :all, examples_hash)
+ # `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_by`, keys will be all the parameter's names.
+ # 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, '' ]
@@ -425,7 +425,7 @@
:password! => { type: String, pattern: /[0-9]{6,10}/, desc: 'password' },
# optional
:remarks => { type: String, desc: 'remarks' },
- }, exp_by: %i[ name password ],
+ }, exp_params: %i[ name password ],
examples: { # ↓ ↓
:right_input => [ 'user1', '123456' ],
:wrong_input => [ 'user2', 'abc' ]
@@ -448,7 +448,7 @@
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_by` and `examples`: for the above example, the following has the same effect:
+ 3. `exp_params` and `examples`: for the above example, the following has the same effect:
```
examples: {
:right_input => { name: 'user1', password: '123456' },
diff --git a/documentation/examples/auto_gen_desc.rb b/documentation/examples/auto_gen_desc.rb
deleted file mode 100644
index 38f6626..0000000
--- a/documentation/examples/auto_gen_desc.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-class V1::GoodsDoc < BaseDoc
- api :index, 'GET list of goods.' do
- desc 'listing goods',
- view!: 'search view, allows:
',
- # '1/ all goods (default):all
' \
- # '2/ only online:online
' \
- # '3/ only offline:offline
' \
- # '4/ expensive goods:expensive
' \
- # '5/ cheap goods:cheap
',
- search_type!: 'search field, allows:
'
- # '1/ name
2/ creator,
3/ category
4/ price
'
-
- # Instead of:
- # query :view, String, enum: %w[ all online offline expensive cheap ]
- query :view, String, enum!: {
- 'all goods (default)': :all,
- 'only online': :online,
- 'only offline': :offline,
- 'expensive goods': :expensive,
- 'cheap goods': :cheap
- }
- query :search_type, String, enum: %w[ name creator category price ]
- # Same as:
- # query :search_type, String, desc!: 'search field, allows:
',
- # enum: %w[ name creator category price ]
-
- # TODO: Support `desc: '', auto_desc: true or %i[ enum must_be ]`
- end
-end
diff --git a/documentation/examples/examples_controller.rb b/documentation/examples/examples_controller.rb
deleted file mode 100644
index 8b267a4..0000000
--- a/documentation/examples/examples_controller.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-class Api::V1::ExamplesController < Api::V1::BaseController
- doc_tag name: 'ExampleTagName', desc: 'ExamplesController\'s APIs'
-
- components do
- schema :DogSchema => [ String, dft: 'doge' ]
- schema :PetSchema => [ not: [ Integer, Boolean ] ]
- query! :UidQuery => [ :uid, String, desc: 'user uid' ]
- path! :IdPath => [ :id, Integer, desc: 'product id' ]
- resp :BadRqResp => [ 'bad request', :json ]
- end
-
-
- api_dry %i[ index show ], 'common parts of :index and :show' do
- header! :Token, String
- response 1000, 'data export', :pdf, type: File
- end
-
-
- api :index, 'GET examples', use: :Token do
- this_api_is_invalid! 'do not use!'
- desc '**GET** list of examples,
and get the status 200.',
- id: 'user id',
- email: 'email addr\'s desc'
- email = 'a@b.c'
-
- query! :count, Integer, enum: 0..5, length: [1, 2], pattern: /^[0-9]$/, range: { gt: 0, le: 5 }
- query! :done, Boolean, must_be: false, default: true, desc: 'must be false'
- query :email, String, lth: :ge_3, default: email # is_a: :email
- file :pdf, 'upload a file: the media type should be application/pdf'
-
- query :test_type, type: String
- query :combination, one_of: [ :DogSchema, String, { type: Integer, desc: 'integer input'}]
- form data: {
- :combination => { any_of: [ Integer, String ] }
- }
-
- response :success, 'success response', :json#, data: :Pet
- security :Token
-
- resp 200, '', :json, data: {
- a: String
- }
- end
-
-
- api :show, skip: :Token do
- param_ref :IdPath, :UidQuery
- response_ref 400 => :BadRqResp
- end
-
-
- api :create do
- form! data: {
- :name! => String, # <= schema_type is `String`
- :password! => { type: String, pattern: /[0-9]{6,10}/, desc: 'password' },
- # optional
- :remarks => { type: String, desc: 'remarks' }, # <= schema_type is `String`, and schema_info is { desc: '..' }
- }
- end
-end
diff --git a/documentation/examples/goods_doc.rb b/documentation/examples/goods_doc.rb
deleted file mode 100644
index 761927c..0000000
--- a/documentation/examples/goods_doc.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-class V2::GoodsDoc < ApiDoc
- SCHEMA_DRY = { a: 1, b: 2 }
-
- # skip: [ 'Token' ] do # you can also skip parameters
- api :index, 'GET list of goods.', use: [ 'Token', :page, :rows ] do # use parameters write in AutoGenDoc#api_dry
- desc 'listing goods',
- view!: 'search view, allows:
',
- search_type!: 'search field, allows:
'
-
- # Single `query`
- query :view, String, enum!: {
- 'all goods (default)': :all,
- 'only online': :online,
- 'only offline': :offline,
- 'expensive goods': :expensive,
- 'cheap goods': :cheap,
- }, **SCHEMA_DRY # >>> Here is a little trick! <<<
- # Batch `query`
- do_query by: {
- :search_type => { type: String, enum: %w[ name creator category price ] },
- :value => String,
- :export => { type: Boolean, desc: 'export as Excel format', examples: {
- :right_input => true,
- :wrong_input => 'wrong input'
- }}
- }
- end
-
-
- api :create, 'POST create a good', use: 'Token' do
- form! data: {
- :name! => { type: String, desc: 'good\'s name' },
- :category_id! => { type: Integer, desc: 'sub_category\'s id', npmt: true, range: { ge: 1 }, as: :cate },
- :price! => { type: Float, desc: 'good\'s price', range: { ge: 0 } },
- # -- optional
- :is_online => { type: Boolean, desc: 'it\'s online?' },
- :remarks => { type: String, desc: 'remarks' },
- :pic_path => { type: String, desc: 'picture url', is: :url },
- },
- exp_by: %i[ name category_id price ],
- examples: {
- :right_input => [ 'good1', 6, 5.7 ],
- :wrong_input => [ 'good2', 0, -1 ]
- }
- end
-
-
- api :show, 'GET the specified Good.', use: [ 'Token', :id ]
-
-
- api :destroy, 'DELETE the specified Good.', use: [ 'Token', :id ]
-end
diff --git a/documentation/parameter.md b/documentation/parameter.md
deleted file mode 100644
index 60ae6c3..0000000
--- a/documentation/parameter.md
+++ /dev/null
@@ -1,69 +0,0 @@
-### More Explanation for `param` and `schema_info`
-
-#### param_type (param_location)
-OpenAPI 3.0 distinguishes between the following parameter types based on the parameter location:
-**header, path, query, cookie**. [more](https://swagger.io/docs/specification/describing-parameters/)
-
-#### name (param_name)
-The name of parameter. It can be Symbol or String.
-
-If param_type is :path, it must correspond to the associated path segment form
-the routing path, for example: if the API path is `/good/:id`, you have to declare a path parameter with name `id` to it.
-
-#### type (schema_type)
-Parameter's (schema) type. We call it `schema_type` because it is inside SchemaObj.
-
-Support all [data types](https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.0.md#dataTypes) defined in OAS.
-
-In addition, you can use `format` in schema_info to define in fine detail the data type being used, like:
-int32, float, date ...
-All the types you can use as following:
- - **String, 'binary', 'base64'**
- - **Integer, Long, 'int32', 'int64', Float, Double**
- - **File** (it will be converted to `{ type: 'string', format: Config.file_format }`)
- - **Date, DateTime**
- - **Boolean**
- - **Array**: `Array[String]` or `[String]`
- - Nested Array: `[[[Integer]]]`
- - **Object**: you can use just `Object`, or use a hash to declare its properties `{ id!: Integer, name: String }`
- (`!` bang key means it is required).
- - Nested Object: `{ id!: Integer, name: { first: String, last: String } }`
- - Nested Array and Object: `[[{ id!: Integer, name: { first: String, last: String } }]]`
- - **:ComponentKey**: pass **Symbol** value to type will generate a Schema Reference Object link
- to the component correspond to ComponentKey, like: :IdPath, :NameQuery
-
- You can use `Object.const_set()` to define a constant that does not exist, but note that
- the value you set could not be a Symbol (it will be explained as a Ref Object), should be a String.
-
-#### required
- :opt or :req
-
-#### Schema Hash
-
-The [[schema]](https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.0.md#schemaObject) defining the type used for the parameter.
-schema_info(optional) will be used to generate Schema Object inside Parameter Object.
-[source code](https://github.com/zhandao/zero-rails_openapi/blob/master/lib/oas_objs/schema_obj.rb)
-You can set the schema by following keys (all are optional), the words in parentheses are available aliases of the keys:
- - **enum (values, allowable_values)**
- Must be Array or Range(will be converted to Array)
- - **must_be (value, allowable_value)**
- Single value, could be a String, Array ...
- - **range (number_range)**
- Allow value in this continuous range. Set this field like this: `{ gt: 0, le: 5 }`
- - **length (lth)**
- Must be an Integer, Integer Array, Integer Range, or the following format Symbol: `:gt_`, `:ge_`, `:lt_`, `:le_`, examples: :ge_5 means "greater than or equal 5"; :lt_9 means "lower than 9".
- - **format (fmt)**
- - **is (is_a)**
- 1. It's not in OAS, just an addition field for better express.You can see it as `format`, but in fact they are quite different.
- 2. Look at this example: the `format` is set to "int32", but you also want to express that this
- schema is an "id" format —— this cannot be expressed in the current OAS version.
- 3. So I suggest that the value of `format` should related to data type, `is` should be an entity.
- 4. ZRO defaults to identify whether `is` patterns matched the name, then automatically generate `is`.
- for example the parameter name "user_email" will generate "is: email". Default `is` options are:
- [email phone password uuid uri url time date], to overwrite it you can set it in initializer `c.is_options = %w[]`.
- 5. If type is Object, for describing each property's schema, the only way is use ref type, like: `{ id: :Id, name: :Name }`
- - **pattern (regexp, pr, reg)**
- Regexp or Time Format
- - **default (dft, default_value)**
- - **as** # TODO
- - **example & examples** # TODO
diff --git a/documentation/examples/auto_gen_doc.rb b/examples/auto_gen_doc.rb
similarity index 100%
rename from documentation/examples/auto_gen_doc.rb
rename to examples/auto_gen_doc.rb
diff --git a/documentation/examples/open_api.rb b/examples/open_api.rb
similarity index 88%
rename from documentation/examples/open_api.rb
rename to examples/open_api.rb
index 471c489..5c29ecb 100644
--- a/documentation/examples/open_api.rb
+++ b/examples/open_api.rb
@@ -1,25 +1,23 @@
require 'open_api'
-OpenApi::Config.tap do |c|
+OpenApi::Config.class_eval do
# Config DSL
- c.instance_eval do
- open_api :zero_rails, base_doc_classes: [ApiDoc]
- info version: '0.0.1', title: 'Zero Rails APIs', description: 'API documentation of Zero-Rails Application.'
- server 'http://localhost:3000', desc: 'Main (production) server'
- server 'http://localhost:3000', desc: 'Internal staging server for testing'
- bearer_auth :Token
- global_auth :Token
- end
+ open_api :zero_rails, base_doc_classes: [ApiDoc]
+ info version: '0.0.1', title: 'Zero Rails APIs', description: 'API documentation of Zero-Rails Application.'
+ server 'http://localhost:3000', desc: 'Main (production) server'
+ server 'http://localhost:3000', desc: 'Internal staging server for testing'
+ bearer_auth :Token
+ global_auth :Token
# [REQUIRED] The location where .json doc file will be output.
- c.file_output_path = 'public/open_api'
+ self.file_output_path = 'public/open_api'
# [Optional] Use this txt instead of running `rails routes`.
- # c.rails_routes_file = 'config/routes.txt'
+ # self.rails_routes_file = 'config/routes.txt'
# Everything about OAS3 is on https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.0.md
# Getting started: https://swagger.io/docs/specification/basic-structure/
- c.open_api_docs = {
+ self.open_api_docs = {
blog_api: {
# [REQUIRED] ZRO will scan all the descendants of the base_doc_classes, then generate their docs.
base_doc_classes: [ApiController],
@@ -96,9 +94,7 @@
end
-Object.const_set('Boolean', 'boolean') # Support `Boolean` writing in DSL
-
-OpenApi.write_docs generate_files: !Rails.env.production?
+OpenApi.write_docs if: !Rails.env.production?
__END__
diff --git a/documentation/examples/output_example.json b/examples/output_example.json
similarity index 100%
rename from documentation/examples/output_example.json
rename to examples/output_example.json
diff --git a/lib/oas_objs/callback_obj.rb b/lib/oas_objs/callback_obj.rb
index 5bd7974..596e5ad 100644
--- a/lib/oas_objs/callback_obj.rb
+++ b/lib/oas_objs/callback_obj.rb
@@ -19,29 +19,22 @@ def initialize(event_name, http_method, callback_url, &block)
def process
{
- self.event_name => {
+ event_name => {
processed_url => {
- self.http_method.downcase.to_sym => processed_block
+ http_method.downcase.to_sym => Api.new.run_dsl(&(self.block || -> { }))
}
}
}
end
def processed_url
- self.callback_url.gsub(/{[^{}]*}/) do |exp|
+ callback_url.gsub(/{[^{}]*}/) do |exp|
key_location, key_name = exp[1..-2].split
connector = key_location == 'body' ? '#/' : '.'
key_location = '$request.' + key_location
['{', key_location, connector, key_name, '}'].join
end
end
-
- def processed_block
- api = Api.new.merge! parameters: [ ], requestBody: '', responses: { }
- api.instance_exec(&(self.block || -> { }))
- api.process_objs
- api.delete_if { |_, v| v.blank? }
- end
end
end
end
diff --git a/lib/oas_objs/combined_schema.rb b/lib/oas_objs/combined_schema.rb
index b7be525..9ab3c30 100644
--- a/lib/oas_objs/combined_schema.rb
+++ b/lib/oas_objs/combined_schema.rb
@@ -5,23 +5,25 @@ module DSL
# https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/
# https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#schemaObject
class CombinedSchema < Hash
- attr_accessor :processed
+ include Helpers
- def initialize(combined_schema)
- self.processed = { }
+ attr_accessor :processed, :mode, :schemas
+ def initialize(combined_schema)
combined_schema.delete_if { |_, v| v.nil? }
- @mode = combined_schema.keys.first.to_s.sub('_not', 'not').camelize(:lower).to_sym
- @schemas = combined_schema.values.first
+ self.mode = combined_schema.keys.first.to_s.camelize(:lower).to_sym
+ self.schemas = combined_schema.values.first
end
- def process(options = { inside_desc: false })
- processed[@mode] = @schemas.map do |schema|
- type = schema.is_a?(Hash) ? schema[:type] : schema
- schema = { } unless schema.is_a?(Hash)
- SchemaObj.new(type, schema).process(options)
- end
- processed
+ def process
+ self.processed = {
+ mode =>
+ schemas.map do |schema|
+ type = schema.is_a?(Hash) ? schema[:type] : schema
+ schema = { } unless schema.is_a?(Hash)
+ SchemaObj.new(type, schema).process
+ end
+ }
end
end
end
diff --git a/lib/oas_objs/example_obj.rb b/lib/oas_objs/example_obj.rb
index 502f0ff..3a3ba29 100644
--- a/lib/oas_objs/example_obj.rb
+++ b/lib/oas_objs/example_obj.rb
@@ -9,7 +9,7 @@ module DSL
class ExampleObj < Hash
include Helpers
- attr_accessor :processed, :examples_hash, :example_value, :keys_of_value
+ attr_accessor :examples_hash, :example_value, :keys_of_value
def initialize(exp, keys_of_value = nil, multiple: false)
multiple ? self.examples_hash = exp : self.example_value = exp
@@ -17,11 +17,11 @@ def initialize(exp, keys_of_value = nil, multiple: false)
end
def process
- return self.processed = example_value if example_value
+ return example_value if example_value
+ return unless examples_hash
- self.processed =
- examples_hash.map do |(name, value)|
- value =
+ examples_hash.map do |(name, value)|
+ value =
if keys_of_value.present? && value.is_a?(Array)
{ value: Hash[keys_of_value.zip(value)] }
elsif value.is_a?(Symbol) && value['$']
@@ -30,8 +30,8 @@ def process
{ value: value }
end
- { name => value }
- end
+ { name => value }
+ end
end
end
end
diff --git a/lib/oas_objs/helpers.rb b/lib/oas_objs/helpers.rb
index 74ff908..1d3e813 100644
--- a/lib/oas_objs/helpers.rb
+++ b/lib/oas_objs/helpers.rb
@@ -14,26 +14,8 @@ def value_present
proc { |_, v| truly_present? v }
end
- # assign.to
- def assign(value)
- @assign = value.is_a?(Symbol) ? send("_#{value}") : value
- self
- end
-
- # reducx.then_merge! => for Hash
- def reducx(*values)
- @assign = values.compact.reduce({ }, :merge!).keep_if &value_present
- self
- end
-
- def to_processed(who)
- return processed unless truly_present?(@assign)
- processed[who.to_sym] = @assign
- processed
- end
-
- def then_merge! # to_processed
- processed.tap { |it| it.merge! @assign if truly_present?(@assign) }
+ def reducing(*values)
+ values.compact.reduce(processed, :merge!).keep_if &value_present
end
end
end
diff --git a/lib/oas_objs/media_type_obj.rb b/lib/oas_objs/media_type_obj.rb
index fbe93ca..b5ffe1d 100644
--- a/lib/oas_objs/media_type_obj.rb
+++ b/lib/oas_objs/media_type_obj.rb
@@ -10,21 +10,20 @@ class MediaTypeObj < Hash
attr_accessor :media_type, :schema, :examples
def initialize(media_type, hash)
- examples_hash = hash.delete(:examples)
- exp_by = hash.delete(:exp_by)
- schema_type = hash.values_at(:type, :data).compact.first
- exp_by = schema_type.keys if exp_by == :all
-
- self.examples = ExampleObj.new(examples_hash, exp_by, multiple: true) if examples_hash.present?
- self.media_type = media_type_mapping media_type
- self.schema = SchemaObj.new(schema_type, hash)
+ examples_hash = hash.delete(:examples)
+ exp_params = schema_type.keys if (exp_params = hash.delete(:exp_params)) == :all
+ self.examples = ExampleObj.new(examples_hash, exp_params, multiple: true) if examples_hash.present?
+ self.media_type = media_type_mapping(media_type)
+ self.schema = SchemaObj.new(hash.values_at(:type, :data).compact.first,
+ hash.except(:type, :data))
end
def process
+ return { } if media_type.nil?
schema_processed = schema.process
result = schema_processed.values.join.blank? ? { } : { schema: schema_processed }
result[:examples] = examples.process unless examples.nil?
- media_type.nil? ? { } : { media_type => result }
+ { media_type => result }
end
# https://swagger.io/docs/specification/media-types/
@@ -48,7 +47,6 @@ def media_type_mapping(media_type)
when :xlsx then 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
when :ppt then 'application/vnd.ms-powerpoint'
when :pptx then 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
- # when :pdf then 'application/pdf'
when :form then 'multipart/form-data'; when :form_data then 'multipart/form-data'
when :text then 'text/*'
when :plain then 'text/plain then charset=utf-8'
diff --git a/lib/oas_objs/param_obj.rb b/lib/oas_objs/param_obj.rb
index 7da6de3..72d0939 100644
--- a/lib/oas_objs/param_obj.rb
+++ b/lib/oas_objs/param_obj.rb
@@ -12,25 +12,20 @@ class ParamObj < Hash
def initialize(name, param_type, type, required, schema)
self.processed = {
- name: name,
- in: param_type,
+ name: name.to_s.delete('!').to_sym,
+ in: param_type.to_s.delete('!'),
required: required.to_s[/req/].present?
}
- self.schema = schema.is_a?(CombinedSchema) ? schema : SchemaObj.new(type, schema)
- merge! schema
+ merge!(self.schema = schema)
end
def process
- assign(desc).to_processed :description
- assign(schema.process).to_processed :schema
+ processed[:schema] = schema.process
+ desc = schema.processed[:description]
+ processed[:description] = desc if desc
processed
end
- def desc
- return self[:desc] || self[:description] if (self[:desc!] || self[:description!]).blank?
- schema.__desc # not a copy of __desc, means desc() will change if schema.__desc changes.
- end
-
def name
processed[:name]
end
diff --git a/lib/oas_objs/request_body_obj.rb b/lib/oas_objs/request_body_obj.rb
index 376b0b5..485ea44 100644
--- a/lib/oas_objs/request_body_obj.rb
+++ b/lib/oas_objs/request_body_obj.rb
@@ -11,18 +11,20 @@ class RequestBodyObj < Hash
include Helpers
attr_accessor :processed, :media_types
+
def initialize(required, desc)
self.media_types = [ ]
self.processed = { required: required['req'].present?, description: desc }
end
- def add_or_fusion(media_type, hash)
+ def absorb(media_type, hash)
media_types << MediaTypeObj.new(media_type, hash)
self
end
def process
- assign(media_types.map(&:process).reduce({ }, &fusion)).to_processed 'content'
+ content = media_types.map(&:process).reduce({ }, &fusion)
+ processed[:content] = content if content.present?
processed
end
end
diff --git a/lib/oas_objs/response_obj.rb b/lib/oas_objs/response_obj.rb
index 0a5f4d9..0f9af7d 100644
--- a/lib/oas_objs/response_obj.rb
+++ b/lib/oas_objs/response_obj.rb
@@ -15,14 +15,15 @@ def initialize(desc)
self.processed = { description: desc }
end
- def add_or_fusion(desc, media_type, hash)
+ def absorb(desc, media_type, hash)
self.processed[:description] = desc if desc.present?
media_types << MediaTypeObj.new(media_type, hash)
self
end
def process
- assign(media_types.map(&:process).reduce({ }, &fusion)).to_processed 'content'
+ content = media_types.map(&:process).reduce({ }, &fusion)
+ processed[:content] = content if content.present?
processed
end
end
diff --git a/lib/oas_objs/schema_obj.rb b/lib/oas_objs/schema_obj.rb
index 933ee4f..5dac009 100644
--- a/lib/oas_objs/schema_obj.rb
+++ b/lib/oas_objs/schema_obj.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
require 'oas_objs/helpers'
-require 'open_api/config'
require 'oas_objs/ref_obj'
require 'oas_objs/example_obj'
require 'oas_objs/schema_obj_helpers'
@@ -13,28 +12,26 @@ class SchemaObj < Hash
include SchemaObjHelpers
include Helpers
- attr_accessor :processed, :type, :preprocessed
+ attr_accessor :processed, :type
- def initialize(type, schema_info)
- self.preprocessed = false
- self.processed = { }
- self.type = type
- merge! schema_info
+ def initialize(type = nil, schema)
+ self.merge!(schema)
+ self.processed = { type: nil, format: nil, **schema.except(:type, :range, :enum!, *SELF_MAPPING.values.flatten) }
+ self.type = type || self[:type]
end
- def process(options = { inside_desc: false })
- processed.merge!(processed_type)
- reducx(additional_properties, enum_and_length, range, format, pattern_default_and_other, desc(options)).then_merge!
+ def process
+ processed.merge!(recg_schema_type)
+ reducing(additional_properties, enum, length, range, format, other, desc)
end
- def desc(inside_desc:)
- result = __desc ? auto_generate_desc : _desc
- return unless inside_desc
+ def desc
+ return unless (result = @bang_enum.present? ? auto_generate_desc : _desc)
{ description: result }
end
- def processed_type(type = self.type)
- t = type.class.in?([Hash, Array, Symbol]) ? type : type.to_s.downcase
+ def recg_schema_type(t = self.type)
+ t = t.class.in?([Hash, Array, Symbol]) ? t : t.to_s.downcase
if t.is_a? Hash
hash_type(t)
elsif t.is_a? Array
@@ -58,93 +55,75 @@ def processed_type(type = self.type)
end
def additional_properties
- return { } if processed[:type] != 'object' || _addProp.nil?
+ return if processed[:type] != 'object' || _addProp.nil?
{
- additionalProperties: SchemaObj.new(_addProp, { }).process(inside_desc: true)
+ additionalProperties: SchemaObj.new(_addProp, { }).process
}
end
- def enum_and_length
- process_enum_info
- process_range_enum_and_lth
-
- # generate length range fields by _lth array
- if (lth = _length || '').is_a?(Array)
- min, max = [lth.first&.to_i, lth.last&.to_i]
- elsif lth['ge']
- min = lth.to_s.split('_').last.to_i
- elsif lth['le']
- max = lth.to_s.split('_').last.to_i
+ def enum
+ self._enum = str_range_to_a(_enum) if _enum.is_a?(Range)
+ # Support this writing for auto generating desc from enum.
+ # enum!: {
+ # 'all_desc': :all,
+ # 'one_desc': :one
+ # }
+ if (@bang_enum = self[:enum!])
+ self._enum ||= @bang_enum.is_a?(Hash) ? @bang_enum.values : @bang_enum
end
+ { enum: _enum }
+ end
+
+ def length
+ return unless _length
+ self._length = str_range_to_a(_length) if _length.is_a?(Range)
- if processed[:type] == 'array'
- { minItems: min, maxItems: max }
+ if _length.is_a?(Array)
+ min, max = [ _length.first&.to_i, _length.last&.to_i ]
else
- { minLength: min, maxLength: max }
- end.merge!(enum: _enum).keep_if &value_present
+ min, max = _length[/ge_(.*)/, 1]&.to_i, _length[/le_(.*)/, 1]&.to_i
+ end
+
+ processed[:type] == 'array' ? { minItems: min, maxItems: max } : { minLength: min, maxLength: max }
end
def range
- range = _range || { }
+ (range = self[:range]) or return
{
minimum: range[:gt] || range[:ge],
- exclusiveMinimum: range[:gt].present? ? true : nil,
+ exclusiveMinimum: range[:gt].present? || nil,
maximum: range[:lt] || range[:le],
- exclusiveMaximum: range[:lt].present? ? true : nil
- }.keep_if &value_present
+ exclusiveMaximum: range[:lt].present? || nil
+ }
end
def format
- result = { is: _is }
- # `format` that generated in process_type() may be overwrote here.
- result[:format] = _format || _is if processed[:format].blank? || _format.present?
- result
+ { format: self[:format] || self[:is_a] } unless processed[:format]
end
- def pattern_default_and_other
+ def other
{
pattern: _pattern.is_a?(String) ? _pattern : _pattern&.inspect&.delete('/'),
- default: _default,
- example: _exp.present? ? ExampleObj.new(_exp).process : nil,
- examples: _exps.present? ? ExampleObj.new(_exps, self[:exp_by], multiple: true).process : nil,
- as: _as, permit: _permit, not_permit: _npermit, req_if: _req_if, opt_if: _opt_if, blankable: _blank
+ example: ExampleObj.new(self[:example]).process,
+ examples: ExampleObj.new(self[:examples], self[:exp_params], multiple: true).process
}
end
- { # SELF_MAPPING
+ SELF_MAPPING = {
_enum: %i[ enum in values allowable_values ],
- _value: %i[ must_be value allowable_value ],
- _range: %i[ range number_range ],
_length: %i[ length lth size ],
- _format: %i[ format fmt ],
- _pattern: %i[ pattern regexp pt reg ],
- _default: %i[ default dft default_value ],
+ _pattern: %i[ pattern regexp ],
_desc: %i[ desc description d ],
- __desc: %i[ desc! description! d! ],
- _exp: %i[ example ],
- _exps: %i[ examples ],
_addProp: %i[ additional_properties add_prop values_type ],
- _is: %i[ is_a is ], # NOT OAS Spec, see documentation/parameter.md
- _as: %i[ as to for map mapping ], # NOT OAS Spec, it's for zero-params_processor
- _permit: %i[ permit pmt ], # ditto
- _npermit: %i[ npmt not_permit unpermit ], # ditto
- _req_if: %i[ req_if req_when ], # ditto
- _opt_if: %i[ opt_if opt_when ], # ditto
- _blank: %i[ blank blankable ], # ditto
}.each do |key, aliases|
- define_method key do
- return self[key] unless self[key].nil?
- aliases.each { |alias_name| self[key] = self[alias_name] if self[key].nil? }
- self[key]
- end
+ define_method(key) { self[key] ||= self.values_at(*aliases).compact.first }
define_method("#{key}=") { |value| self[key] = value }
end
end
end
end
-
__END__
Schema Object Examples
diff --git a/lib/oas_objs/schema_obj_helpers.rb b/lib/oas_objs/schema_obj_helpers.rb
index ea0c707..3de6f38 100644
--- a/lib/oas_objs/schema_obj_helpers.rb
+++ b/lib/oas_objs/schema_obj_helpers.rb
@@ -9,11 +9,11 @@ def hash_type(t)
# id!: { type: Integer, enum: 0..5, desc: 'user id' }
# }, should have description within schema
if t.key?(:type)
- SchemaObj.new(t[:type], t).process(inside_desc: true)
+ SchemaObj.new(t[:type], t).process
# For supporting combined schema in nested schema.
elsif (t.keys & %i[ one_of any_of all_of not ]).present?
- CombinedSchema.new(t).process(inside_desc: true)
+ CombinedSchema.new(t).process
else
obj_type(t)
end
@@ -24,59 +24,33 @@ def obj_type(t)
t.each do |prop_name, prop_type|
obj_type[:required] << prop_name.to_s.delete('!') if prop_name['!']
- obj_type[:properties][prop_name.to_s.delete('!').to_sym] = processed_type(prop_type)
+ obj_type[:properties][prop_name.to_s.delete('!').to_sym] = recg_schema_type(prop_type)
end
obj_type.keep_if &value_present
end
def array_type(t)
- t = t.size == 1 ? t.first : { one_of: t }
{
type: 'array',
- items: processed_type(t)
+ items: recg_schema_type(t.one? ? t[0] : { one_of: t })
}
end
- def process_range_enum_and_lth
- self[:_enum] = str_range_to_a(_enum) if _enum.is_a?(Range)
- self[:_length] = str_range_to_a(_length) if _length.is_a?(Range)
-
- values = _enum || _value
- self._enum = Array(values) if truly_present?(values)
- end
-
def str_range_to_a(val)
val_class = val.first.class
action = :"to_#{val_class.to_s.downcase[0]}"
(val.first.to_s..val.last.to_s).to_a.map(&action)
end
- def process_enum_info
- # Support this writing for auto generating desc from enum.
- # enum!: {
- # 'all_desc': :all,
- # 'one_desc': :one
- # }
- self._enum ||= (e = self[:enum!])
- return unless e.is_a? Hash
- @enum_info = e
- self._enum = e.values
- end
-
- # TODO: more info and desc configure
def auto_generate_desc
- return __desc if _enum.blank?
-
- if @enum_info.present?
- @enum_info.each_with_index do |(info, value), index|
- self.__desc = __desc + "
#{index + 1}/ #{info}: #{value}"
+ if @bang_enum.is_a?(Hash)
+ @bang_enum.each_with_index do |(info, value), index|
+ self._desc = _desc + "
#{index + 1}/ #{info}: #{value}"
end
else
- _enum.each_with_index do |value, index|
- self.__desc = __desc + "
#{index + 1}/ #{value}"
- end
+ @bang_enum.each_with_index { |value, index| self._desc = _desc + "
#{index + 1}/ #{value}" }
end
- __desc
+ _desc
end
end
end
diff --git a/lib/open_api.rb b/lib/open_api.rb
index fceeea3..cde669c 100644
--- a/lib/open_api.rb
+++ b/lib/open_api.rb
@@ -1,18 +1,67 @@
# frozen_string_literal: true
+require 'colorize'
+
require 'open_api/version'
+require 'open_api/support/tip'
require 'open_api/config'
-require 'open_api/generator'
+require 'open_api/router'
require 'open_api/dsl'
module OpenApi
- include Generator
+ module_function
+ cattr_accessor :routes_index, default: { }
+ cattr_accessor :docs, default: { }
+
+ def write_docs(if: true, read_on_controller: true)
+ (docs = generate_docs(read_on_controller)) and Tip.loaded
+ return unless binding.local_variable_get :if
+
+ FileUtils.mkdir_p Config.file_output_path
+ docs.each do |name, doc|
+ File.write "#{Config.file_output_path}/#{name}.json", JSON.pretty_generate(doc)
+ Tip.generated(name.to_s.rjust(docs.keys.map(&:size).max))
+ end
+ end
+
+ def generate_docs(read_on_controller)
+ return Tip.no_config if Config.docs.keys.blank?
+ traverse_controllers if read_on_controller
+ Dir[*Array(Config.doc_location)].each { |file| require file }
+ Config.docs.keys.map { |name| [ name, generate_doc(name) ] }.to_h
+ end
+
+ def generate_doc(doc_name)
+ settings, doc = init_hash(doc_name)
+ [*(bdc = settings[:base_doc_classes]), *bdc.flat_map(&:descendants)].each do |kls|
+ next if kls.oas[:doc].blank?
+ doc[:paths].merge!(kls.oas[:apis])
+ doc[:tags] << kls.oas[:doc][:tag]
+ doc[:components].deep_merge!(kls.oas[:doc][:components] || { })
+ OpenApi.routes_index[kls.oas[:route_base]] = doc_name
+ end
+
+ doc[:components].delete_if { |_, v| v.blank? }
+ doc[:tags] = doc[:tags].sort { |a, b| a[:name] <=> b[:name] }
+ doc[:paths] = doc[:paths].sort.to_h
+ OpenApi.docs[doc_name] = doc#.delete_if { |_, v| v.blank? }
+ end
- cattr_accessor :routes_index do
- { }
+ def init_hash(doc_name)
+ settings = Config.docs[doc_name]
+ doc = { openapi: '3.0.0', **settings.slice(:info, :servers) }.merge!(
+ security: settings[:global_security], tags: [ ], paths: { },
+ components: {
+ securitySchemes: settings[:securitySchemes] || { },
+ schemas: { }, parameters: { }, requestBodies: { }
+ }
+ )
+ [ settings, doc ]
end
- cattr_accessor :docs do
- { }
+ def traverse_controllers
+ Dir['./app/controllers/**/*_controller.rb'].each do |file|
+ file.sub('./app/controllers/', '').sub('.rb', '').camelize.constantize
+ end
end
end
diff --git a/lib/open_api/config.rb b/lib/open_api/config.rb
index f928f79..7e8fa0c 100644
--- a/lib/open_api/config.rb
+++ b/lib/open_api/config.rb
@@ -7,51 +7,22 @@ module OpenApi
module Config
include ConfigDSL
+ cattr_accessor :default_run_dry, default: false
+
# [REQUIRED] The location where .json doc file will be output.
- cattr_accessor :file_output_path do
- 'public/open_api'
- end
+ cattr_accessor :file_output_path, default: 'public/open_api'
- cattr_accessor :generate_doc do
- true
- end
+ cattr_accessor :doc_location, default: ['./app/**/*_doc.rb']
- cattr_accessor :doc_location do
- ['./app/**/*_doc.rb']
- end
+ cattr_accessor :rails_routes_file
- cattr_accessor :rails_routes_file do
- nil
- end
-
- cattr_accessor :active_record_base do
- nil
- end
+ cattr_accessor :model_base
# Everything about OAS3 is on https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.0.md
# Getting started: https://swagger.io/docs/specification/basic-structure/
- cattr_accessor :open_api_docs do
- {
- # # [REQUIRED] At least one doc.
- # zero_rails: {
- # # [REQUIRED] ZRO will scan all the descendants of the base_doc_classes, and then generate their docs.
- # base_doc_classes: [ApplicationController],
- #
- # # [REQUIRED] Info Object: The info section contains API information
- # info: {
- # # [REQUIRED] The title of the application.
- # title: 'Zero Rails Apis',
- # # [REQUIRED] The version of the OpenAPI document
- # # (which is distinct from the OpenAPI Specification version or the API implementation version).
- # version: '0.0.1'
- # }
- # }
- }
- end
+ cattr_accessor :open_api_docs, default: { }
- cattr_accessor :file_format do
- 'binary'
- end
+ cattr_accessor :file_format, default: 'binary'
def self.docs
open_api_docs
diff --git a/lib/open_api/dsl.rb b/lib/open_api/dsl.rb
index 02f8d4c..eb31945 100644
--- a/lib/open_api/dsl.rb
+++ b/lib/open_api/dsl.rb
@@ -2,74 +2,59 @@
require 'open_api/dsl/api'
require 'open_api/dsl/components'
-require 'colorize'
module OpenApi
module DSL
- def self.included(base)
- base.extend ClassMethods
- end
+ extend ActiveSupport::Concern
+
+ class_methods do
+ def oas
+ @oas ||= { doc: { }, dry_blocks: { }, apis: { }, route_base: try(:controller_path),
+ tag_name: try(:controller_name)&.camelize }
+ end
- # TODO: Doc-Block Comments
- module ClassMethods
def route_base path
- @route_base = path
- @doc_tag = path.split('/').last.camelize
+ oas[:route_base] = path
+ oas[:tag_name] = path.split('/').last.camelize
end
- def doc_tag name: nil, desc: '', external_doc_url: nil
- # apis will group by the tags.
- @doc_tag = name if name.present?
- @doc_tag ||= controller_name.camelize
- tag = (@doc_info = { })[:tag] = { name: @doc_tag }
- tag[:description] = desc if desc.present?
- tag[:externalDocs] = { description: 'ref', url: external_doc_url } if external_doc_url
+ # APIs will be grouped by tags.
+ def doc_tag name: nil, **tag_info # description: ..., externalDocs: ...
+ oas[:doc][:tag] = { name: name || oas[:tag_name], **tag_info }
end
def components &block
- doc_tag if @doc_info.nil?
- structure = %i[ schemas responses parameters examples requestBodies securitySchemes ].map { |k| [k, { }] }.to_h
- current_doc = Components.new.merge!(structure)
- current_doc.instance_exec(&block)
- current_doc.process_objs
-
- (@doc_info[:components] ||= { }).deep_merge!(current_doc)
+ doc_tag if oas[:doc].blank?
+ (components = Components.new).instance_exec(&block)
+ components.process_objs
+ (oas[:doc][:components] ||= { }).deep_merge!(components)
end
- def api action, summary = '', id: nil, tag: nil, http: http_method = nil, skip: [ ], use: [ ], &block
- doc_tag if @doc_info.nil?
- # select the routing info (corresponding to the current method) from routing list.
- action_path = "#{@route_base ||= controller_path}##{action}"
- routes = ctrl_routes_list&.select { |api| api[:action_path][/^#{action_path}$/].present? }
- return puts ' ZRO'.red + " Route mapping failed: #{action_path}" if routes.blank?
+ def api action, summary = '', id: nil, tag: nil, http: nil, dry: Config.default_run_dry, &block
+ doc_tag if oas[:doc].blank?
+ action_path = "#{oas[:route_base]}##{action}"
+ routes = Router.routes_list[oas[:route_base]]
+ &.select { |api| api[:action_path][/^#{action_path}$/].present? }
+ return Tip.no_route(action_path) if routes.blank?
+
+ tag = tag || oas[:doc][:tag][:name]
+ api = Api.new(action_path, summary: summary, tags: [tag], id: id || "#{tag}_#{action.to_s.camelize}")
+ [action, tag, :all].each { |key| api.dry_blocks.concat(oas[:dry_blocks][key] || [ ]) }
+ api.run_dsl(dry: dry, &block)
+ _set_apis(api, routes, http)
+ end
- api = Api.new(action_path, skip: Array(skip), use: Array(use))
- .merge! description: '', summary: summary, operationId: id || "#{@doc_info[:tag][:name]}_#{action.to_s.camelize}",
- tags: [tag || @doc_tag], parameters: [ ], requestBody: '', responses: { }, callbacks: { },
- links: { }, security: [ ], servers: [ ]
- [action, :all].each { |blk_key| @zro_dry_blocks&.[](blk_key)&.each { |blk| api.instance_eval(&blk) } }
- api.param_use = api.param_skip = [ ] # `skip` and `use` only affect `api_dry`'s blocks
- api.instance_exec(&block) if block_given?
- api.process_objs
- api.delete_if { |_, v| v.blank? }
+ def api_dry action_or_tags = :all, &block
+ Array(action_or_tags).each { |a| (oas[:dry_blocks][a.to_sym] ||= [ ]) << block }
+ end
+ def _set_apis(api, routes, http)
routes.each do |route|
- path = (@api_info ||= { })[route[:path]] ||= { }
+ path = oas[:apis][route[:path]] ||= { }
(http || route[:http_verb]).split('|').each { |verb| path[verb] = api }
end
-
api
end
-
- # method could be symbol array, like: %i[ .. ]
- def api_dry action = :all, desc = '', &block
- @zro_dry_blocks ||= { }
- Array(action).each { |a| (@zro_dry_blocks[a.to_sym] ||= [ ]) << block }
- end
-
- def ctrl_routes_list
- Generator.routes_list[@route_base]
- end
end
end
end
diff --git a/lib/open_api/dsl/api.rb b/lib/open_api/dsl/api.rb
index fa079f1..c9de713 100644
--- a/lib/open_api/dsl/api.rb
+++ b/lib/open_api/dsl/api.rb
@@ -1,23 +1,22 @@
# frozen_string_literal: true
-require 'open_api/dsl/common_dsl'
+require 'open_api/dsl/helpers'
module OpenApi
module DSL
class Api < Hash
- include DSL::CommonDSL
include DSL::Helpers
- attr_accessor :action_path, :param_skip, :param_use, :param_descs, :param_order
+ attr_accessor :action_path, :dry_skip, :dry_only, :dry_blocks, :dryed, :param_order
- def initialize(action_path = '', skip: [ ], use: [ ])
+ def initialize(action_path = '', summary: nil, tags: [ ], id: nil)
self.action_path = action_path
- self.param_skip = skip
- self.param_use = use
- self.param_descs = { }
+ self.dry_blocks = [ ]
+ self.merge!(summary: summary, operationId: id, tags: tags, description: '', parameters: [ ],
+ requestBody: nil, responses: { }, callbacks: { }, links: { }, security: [ ], servers: [ ])
end
- def this_api_is_invalid! explain = ''
+ def this_api_is_invalid!(*)
self[:deprecated] = true
end
@@ -25,41 +24,41 @@ def this_api_is_invalid! explain = ''
alias this_api_is_unused! this_api_is_invalid!
alias this_api_is_under_repair! this_api_is_invalid!
- def desc desc, param_descs = { }
- self.param_descs = param_descs
+ def desc desc
self[:description] = desc
end
- def param param_type, name, type, required, schema_info = { }
- return if param_skip.include?(name)
- return if param_use.present? && param_use.exclude?(name)
+ alias description desc
- schema_info[:desc] ||= param_descs[name]
- schema_info[:desc!] ||= param_descs[:"#{name}!"]
- param_obj = ParamObj.new(name, param_type, type, required, schema_info)
- # The definition of the same name parameter will be overwritten
- fill_in_parameters(param_obj)
+ def dry only: nil, skip: nil, none: false
+ return if dry_blocks.blank? || dryed
+ self.dry_skip = skip && Array(skip)
+ self.dry_only = none ? [:none] : only && Array(only)
+ dry_blocks.each { |blk| instance_eval(&blk) }
+ self.dry_skip = self.dry_only = nil
+ self.dryed = true
end
- # [ header header! path path! query query! cookie cookie! ]
- def _param_agent name, type = nil, **schema_info
- schema = process_schema_info(type, schema_info)
- return puts ' ZRO'.red + " Syntax Error: param `#{name}` has no schema type!" if schema[:illegal?]
- param @param_type, name, schema[:type], @necessity, schema[:combined] || schema[:info]
+ def param param_type, name, type, required, schema = { }
+ return if dry_skip&.include?(name) || dry_only&.exclude?(name)
+
+ return unless schema = process_schema_input(type, schema, name)
+ param_obj = ParamObj.new(name, param_type, type, required, schema)
+ # The definition of the same name parameter will be overwritten
+ index = self[:parameters].map(&:name).index(param_obj.name)
+ index ? self[:parameters][index] = param_obj : self[:parameters] << param_obj
end
- # For supporting this: (just like `form '', data: { }` usage)
- # do_query by: {
- # :search_type => { type: String },
- # :export! => { type: Boolean }
- # }
+ alias parameter param
+
%i[ header header! path path! query query! cookie cookie! ].each do |param_type|
- define_method "do_#{param_type}" do |by:, **common_schema|
- by.each do |param_name, schema|
- action = "#{param_type}#{param_name['!']}".sub('!!', '!')
- type, schema = schema.is_a?(Hash) ? [schema[:type], schema] : [schema, { }]
- args = [ param_name.to_s.delete('!').to_sym, type, schema.reverse_merge!(common_schema) ]
- send(action, *args)
+ define_method param_type do |name, type = nil, **schema|
+ param param_type, name, type, (param_type['!'] ? :req : :opt), schema
+ end
+
+ define_method "in_#{param_type}" do |params|
+ params.each_pair do |param_name, schema|
+ param param_type, param_name, nil, (param_type['!'] || param_name['!'] ? :req : :opt), schema
end
end
end
@@ -68,22 +67,21 @@ def param_ref component_key, *keys
self[:parameters] += [component_key, *keys].map { |key| RefObj.new(:parameter, key) }
end
- # options: `exp_by` and `examples`
- def request_body required, media_type, data: { }, **options
- desc = options.delete(:desc) || ''
- self[:requestBody] = RequestBodyObj.new(required, desc) unless self[:requestBody].is_a?(RequestBodyObj)
- self[:requestBody].add_or_fusion(media_type, { data: data , **options })
- end
-
- # [ body body! ]
- def _request_body_agent media_type, data: { }, **options
- request_body @necessity, media_type, data: data, **options
+ # options: `exp_params` and `examples`
+ def request_body required, media_type, data: { }, desc: '', **options
+ (self[:requestBody] ||= RequestBodyObj.new(required, desc)).absorb(media_type, { data: data , **options })
end
def body_ref component_key
self[:requestBody] = RefObj.new(:requestBody, component_key)
end
+ %i[ body body! ].each do |method|
+ define_method method do |media_type, data: { }, **options|
+ request_body (method['!'] ? :req : :opt), media_type, data: data, **options
+ end
+ end
+
def form data:, **options
body :form, data: data, **options
end
@@ -92,21 +90,20 @@ def form! data:, **options
body! :form, data: data, **options
end
- def data name, type = nil, schema_info = { }
- schema_info[:type] = type if type.present?
- form data: { name => schema_info }
+ def data name, type = nil, schema = { }
+ schema[:type] = type if type.present?
+ form data: { name => schema }
end
- def file media_type, data: { type: File }, **options
- body media_type, data: data, **options
+ def response code, desc, media_type = nil, data: { }
+ (self[:responses][code] ||= ResponseObj.new(desc)).absorb(desc, media_type, { data: data })
end
- def file! media_type, data: { type: File }, **options
- body! media_type, data: data, **options
- end
+ alias_method :resp, :response
+ alias_method :error, :response
- def response_ref code_compkey_hash
- code_compkey_hash.each { |code, component_key| self[:responses][code] = RefObj.new(:response, component_key) }
+ def response_ref code_and_compkey # = { }
+ code_and_compkey.each { |code, component_key| self[:responses][code] = RefObj.new(:response, component_key) }
end
def security_require scheme_name, scopes: [ ]
@@ -115,7 +112,7 @@ def security_require scheme_name, scopes: [ ]
alias security security_require
alias auth security_require
- alias need_auth security_require
+ alias auth_with security_require
def callback event_name, http_method, callback_url, &block
self[:callbacks].deep_merge! CallbackObj.new(event_name, http_method, callback_url, &block).process
@@ -125,26 +122,22 @@ def server url, desc: ''
self[:servers] << { url: url, description: desc }
end
- def order *param_names
- self.param_order = param_names
- # be used when `api_dry`
- self.param_use = param_order if param_use.blank?
- self.param_skip = param_use - param_order
- end
-
- def param_examples exp_by = :all, examples_hash
- exp_by = self[:parameters].map(&:name) if exp_by == :all
- self[:examples] = ExampleObj.new(examples_hash, exp_by, multiple: true).process
+ def param_examples exp_params = :all, examples_hash
+ exp_params = self[:parameters].map(&:name) if exp_params == :all
+ self[:examples] = ExampleObj.new(examples_hash, exp_params, multiple: true).process
end
alias examples param_examples
- def process_objs
- self[:parameters].map!(&:process)
- self[:parameters].sort_by! { |param| param_order.index(param[:name]) || Float::INFINITY } if param_order.present?
+ def run_dsl(dry: false, &block)
+ instance_exec(&block) if block_given?
+ dry() if dry
+ self[:parameters].map!(&:process)
self[:requestBody] = self[:requestBody].try(:process)
self[:responses].each { |code, response| self[:responses][code] = response.process }
+ self[:responses] = self[:responses].sort.to_h
+ self.delete_if { |_, v| v.blank? }
end
end
end
diff --git a/lib/open_api/dsl/common_dsl.rb b/lib/open_api/dsl/common_dsl.rb
deleted file mode 100644
index 9b70817..0000000
--- a/lib/open_api/dsl/common_dsl.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-# frozen_string_literal: true
-
-require 'oas_objs/schema_obj'
-require 'oas_objs/combined_schema'
-require 'oas_objs/param_obj'
-require 'oas_objs/response_obj'
-require 'oas_objs/request_body_obj'
-require 'oas_objs/ref_obj'
-require 'oas_objs/example_obj'
-require 'oas_objs/callback_obj'
-require 'open_api/dsl/helpers'
-
-module OpenApi
- module DSL
- module CommonDSL
- %i[ header header! path path! query query! cookie cookie! ].each do |param_type|
- define_method param_type do |*args|
- @necessity = param_type['!'] ? :req : :opt
- @param_type = param_type.to_s.delete('!') # OR: caller[0][/`.*'/][1..-2].to_sym
- _param_agent *args
- end
- end
-
- %i[ body body! ].each do |method|
- define_method method do |*args|
- @necessity = method['!'] ? :req : :opt
- _request_body_agent *args
- end
- end
-
- # `code`: when defining components, `code` means `component_key`
- def response code, desc, media_type = nil, data: { }, type: nil
- self[:responses][code] = ResponseObj.new(desc) unless (self[:responses] ||= { })[code].is_a?(ResponseObj)
- self[:responses][code].add_or_fusion(desc, media_type, { data: type || data })
- end
-
- alias_method :resp, :response
- alias_method :error, :response
- end
- end
-end
diff --git a/lib/open_api/dsl/components.rb b/lib/open_api/dsl/components.rb
index e780366..2350188 100644
--- a/lib/open_api/dsl/components.rb
+++ b/lib/open_api/dsl/components.rb
@@ -1,17 +1,19 @@
# frozen_string_literal: true
-require 'open_api/dsl/common_dsl'
+require 'open_api/dsl/helpers'
module OpenApi
module DSL
class Components < Hash
- include DSL::CommonDSL
include DSL::Helpers
- def schema component_key, type = nil, **schema_info
- schema = process_schema_info(type, schema_info, model: component_key)
- return puts ' ZRO'.red + " Syntax Error: component schema `#{component_key}` has no type!" if schema[:illegal?]
- self[:schemas][component_key.to_s.to_sym] = (schema[:combined] or SchemaObj.new(type = schema[:info], { })).process
+ def initialize
+ merge!(%i[ schemas responses parameters examples requestBodies securitySchemes ].map { |k| [ k, { } ] }.to_h)
+ end
+
+ def schema component_key, type = nil, **schema
+ return unless schema = process_schema_input(type, schema, component_key, model: component_key)
+ self[:schemas][component_key.to_s.to_sym] = schema.process
end
arrow_enable :schema
@@ -22,31 +24,37 @@ def example component_key, examples_hash
arrow_enable :example
- def param component_key, param_type, name, type, required, schema_info = { }
- self[:parameters][component_key] = ParamObj.new(name, param_type, type, required, schema_info).process
+ def param component_key, param_type, name, type, required, schema = { }
+ return unless schema = process_schema_input(type, schema, name)
+ self[:parameters][component_key] = ParamObj.new(name, param_type, type, required, schema).process
end
- # [ header header! path path! query query! cookie cookie! ]
- def _param_agent component_key, name, type = nil, **schema_info
- schema = process_schema_info(type, schema_info)
- return puts ' ZRO'.red + " Syntax Error: param `#{name}` has no schema type!" if schema[:illegal?]
- param component_key, @param_type, name, schema[:type], @necessity, schema[:combined] || schema[:info]
+ %i[ header header! path path! query query! cookie cookie! ].each do |param_type|
+ define_method param_type do |component_key, name, type = nil, **schema|
+ param component_key, param_type, name, type, (param_type['!'] ? :req : :opt), schema
+ end
+ arrow_enable param_type
end
- arrow_enable :_param_agent
-
def request_body component_key, required, media_type, data: { }, desc: '', **options
- cur = self[:requestBodies][component_key]
- cur = RequestBodyObj.new(required, desc) unless cur.is_a?(RequestBodyObj)
- self[:requestBodies][component_key] = cur.add_or_fusion(media_type, { data: data, **options })
+ (self[:requestBodies][component_key] ||= RequestBodyObj.new(required, desc)).absorb(media_type, { data: data, **options })
end
- # [ body body! ]
- def _request_body_agent component_key, media_type, data: { }, **options
- request_body component_key, @necessity, media_type, data: data, **options
+ %i[ body body! ].each do |method|
+ define_method method do |component_key, media_type, data: { }, **options|
+ request_body component_key, (method['!'] ? :req : :opt), media_type, data: data, **options
+ end
+ end
+
+ arrow_enable :body
+ arrow_enable :body!
+
+ def response component_key, desc, media_type = nil, data: { }
+ (self[:responses][component_key] ||= ResponseObj.new(desc)).absorb(desc, media_type, { data: data })
end
- arrow_enable :_request_body_agent
+ alias_method :resp, :response
+ alias_method :error, :response
arrow_enable :resp
arrow_enable :response
@@ -73,8 +81,7 @@ def bearer_auth scheme_name, format = 'JWT', other_info = { }
arrow_enable :bearer_auth
def api_key scheme_name, field:, in: 'header', **other_info
- _in = binding.local_variable_get(:in)
- security_scheme scheme_name, { type: 'apiKey', name: field, in: _in, **other_info }
+ security_scheme scheme_name, { type: 'apiKey', name: field, in: binding.local_variable_get(:in), **other_info }
end
arrow_enable :api_key
@@ -82,6 +89,7 @@ def api_key scheme_name, field:, in: 'header', **other_info
def process_objs
self[:requestBodies].each { |key, body| self[:requestBodies][key] = body.process }
self[:responses].each { |code, response| self[:responses][code] = response.process }
+ self.delete_if { |_, v| v.blank? }
end
end
end
diff --git a/lib/open_api/dsl/helpers.rb b/lib/open_api/dsl/helpers.rb
index 4e5940c..e92581d 100644
--- a/lib/open_api/dsl/helpers.rb
+++ b/lib/open_api/dsl/helpers.rb
@@ -1,67 +1,39 @@
# frozen_string_literal: true
+require 'oas_objs/schema_obj'
+require 'oas_objs/combined_schema'
+require 'oas_objs/param_obj'
+require 'oas_objs/response_obj'
+require 'oas_objs/request_body_obj'
+require 'oas_objs/ref_obj'
+require 'oas_objs/example_obj'
+require 'oas_objs/callback_obj'
+
module OpenApi
module DSL
module Helpers
- def self.included(base)
- base.extend ClassMethods
- end
+ extend ActiveSupport::Concern
- # :nocov:
def load_schema(model) # TODO: test
- # About `show_attrs`, see:
- # (1) BuilderSupport module: https://github.com/zhandao/zero-rails/blob/master/app/models/concerns/builder_support.rb
- # (2) config in model: https://github.com/zhandao/zero-rails/tree/master/app/models/good.rb
- # (3) jbuilder file: https://github.com/zhandao/zero-rails/blob/master/app/views/api/v1/goods/index.json.jbuilder
- # In a word, BuilderSupport let you control the `output fields and nested association infos` very easily.
- if model.respond_to? :show_attrs
- _load_schema_based_on_show_attr(model)
- else
- model.columns.map { |column| _type_mapping(column) }
- end.compact.reduce({ }, :merge!) rescue ''
- end
-
- def _type_mapping(column)
- type = column.sql_type_metadata.type.to_s.camelize
- type = 'DateTime' if type == 'Datetime'
- { column.name.to_sym => Object.const_get(type) }
- end
-
- def _load_schema_based_on_show_attr(model)
- columns = model.column_names.map(&:to_sym)
- model.show_attrs.map do |attr|
- if columns.include?(attr)
- index = columns.index(attr)
- _type_mapping(model.columns[index])
- elsif attr[/_info/]
- # TODO: 如何获知关系是 many?因为不能只判断结尾是否 ‘s’
- assoc_model = Object.const_get(attr.to_s.split('_').first.singularize.camelize)
- { attr => load_schema(assoc_model) }
- end rescue next
- end
- end
- # :nocov:
-
- def fill_in_parameters(param_obj)
- index = self[:parameters].map(&:name).index(param_obj.name)
- index.present? ? self[:parameters][index] = param_obj : self[:parameters] << param_obj
+ return unless Config.model_base && model.try(:superclass) == Config.model_base
+ model.columns.map do |column|
+ type = column.sql_type_metadata.type.to_s.camelize
+ type = 'DateTime' if type == 'Datetime'
+ [ column.name.to_sym, Object.const_get(type) ]
+ end.to_h rescue ''
end
def _combined_schema(one_of: nil, all_of: nil, any_of: nil, not: nil, **other)
input = (_not = binding.local_variable_get(:not)) || one_of || all_of || any_of
- CombinedSchema.new(one_of: one_of, all_of: all_of, any_of: any_of, _not: _not) if input
+ CombinedSchema.new(one_of: one_of, all_of: all_of, any_of: any_of, not: _not) if input
end
- def process_schema_info(schema_type, schema_info, model: nil)
- combined_schema = _combined_schema(schema_info)
- type = schema_info[:type] ||= schema_type
- schema_info = load_schema(model) if model.try(:superclass) == (Config.active_record_base || ApplicationRecord)
- {
- illegal?: type.nil? && combined_schema.nil?,
- combined: combined_schema,
- info: schema_info,
- type: type
- }
+ def process_schema_input(schema_type, schema, name, model: nil)
+ schema = { type: schema } unless schema.is_a?(Hash)
+ combined_schema = _combined_schema(schema)
+ type = schema[:type] ||= schema_type
+ return Tip.param_no_type(name) if type.nil? && combined_schema.nil?
+ combined_schema || SchemaObj.new(type, load_schema(model) || schema)
end
# Arrow Writing:
@@ -77,7 +49,7 @@ def arrow_writing_support
end
end
- module ClassMethods
+ class_methods do
def arrow_enable method
alias_method :"_#{method}", method
define_method method do |*args|
diff --git a/lib/open_api/generator.rb b/lib/open_api/generator.rb
deleted file mode 100644
index a920f01..0000000
--- a/lib/open_api/generator.rb
+++ /dev/null
@@ -1,118 +0,0 @@
-# frozen_string_literal: true
-
-require 'open_api/config'
-require 'colorize'
-
-module OpenApi
- module Generator
- module_function
-
- def self.included(base)
- base.extend ClassMethods
- end
-
- module ClassMethods
- def generate_docs(doc_name = nil)
- return puts ' ZRO'.red + ' No documents have been configured!' if Config.docs.keys.blank?
-
- # TODO
- # :nocov:
- Dir['./app/controllers/**/*_controller.rb'].each do |file|
- file.sub('./app/controllers/', '').sub('.rb', '').camelize.constantize
- end
- # :nocov:
- Dir[*Array(Config.doc_location)].each { |file| require file }
- (doc_name || Config.docs.keys).map { |name| { name => generate_doc(name) } }.reduce({ }, :merge!)
- end
-
- def generate_doc(doc_name)
- settings = Config.docs[doc_name]
- doc = { openapi: '3.0.0', **settings.slice(:info, :servers) }.merge!(
- security: settings[:global_security], tags: [ ], paths: { },
- components: {
- securitySchemes: settings[:securitySchemes] || { },
- schemas: { }, parameters: { }, requestBodies: { }
- }
- )
-
- [*(bdc = settings[:base_doc_classes]), *bdc.flat_map(&:descendants)].each do |ctrl|
- doc_info = ctrl.instance_variable_get('@doc_info')
- next if doc_info.nil?
-
- doc[:paths].merge!(ctrl.instance_variable_get('@api_info') || { })
- doc[:tags] << doc_info[:tag]
- doc[:components].deep_merge!(doc_info[:components] || { })
- OpenApi.routes_index[ctrl.instance_variable_get('@route_base')] = doc_name
- end
-
- doc[:components].delete_if { |_, v| v.blank? }
- doc[:tags] = doc[:tags].sort { |a, b| a[:name] <=> b[:name] }
- doc[:paths] = doc[:paths].sort.to_h
-
- OpenApi.docs[doc_name] = doc#.delete_if { |_, v| v.blank? }
- end
-
- def write_docs(generate_files: true)
- docs = generate_docs
- puts ' ZRO loaded.'.green if ENV['RAILS_ENV']
- return unless generate_files
- # :nocov:
- output_path = Config.file_output_path
- FileUtils.mkdir_p output_path
- max_length = docs.keys.map(&:size).sort.last
- docs.each do |doc_name, doc|
- puts ' ZRO'.green + " `#{doc_name.to_s.rjust(max_length)}.json` has been generated."
- File.open("#{output_path}/#{doc_name}.json", 'w') { |file| file.write JSON.pretty_generate doc }
- end
- # :nocov:
- end
- end
- # end of module
-
- def routes
- @routes ||=
- if (file = Config.rails_routes_file)
- File.read(file)
- else
- # :nocov:
- # ref https://github.com/rails/rails/blob/master/railties/lib/rails/tasks/routes.rake
- require './config/routes'
- all_routes = Rails.application.routes.routes
- require 'action_dispatch/routing/inspector'
- inspector = ActionDispatch::Routing::RoutesInspector.new(all_routes)
- inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, nil)
- # :nocov:
- end
- end
-
- def routes_list
- @routes_list ||= routes.split("\n").drop(1).map do |line|
- next unless line['#']
- infos = line.match(/[A-Z|].*/).to_s.split(' ') # => [GET, /api/v1/examples/:id, api/v1/examples#index]
-
- {
- http_verb: infos[0].downcase, # => "get" / "get|post"
- path: infos[1][0..-11].split('/').map do |item|
- item[':'] ? "{#{item[1..-1]}}" : item
- end.join('/'), # => "/api/v1/examples/{id}"
- action_path: infos[2] # => "api/v1/examples#index"
- } rescue next
- end.compact.group_by { |api| api[:action_path].split('#').first } # => { "api/v1/examples" => [..] }, group by paths
- end
-
- def get_actions_by_route_base(route_base)
- routes_list[route_base]&.map do |action_info|
- action_info[:action_path].split('#').last
- end
- end
-
- def find_path_httpverb_by(route_base, action)
- routes_list[route_base]&.map do |action_info|
- if action_info[:action_path].split('#').last == action.to_s
- return [ action_info[:path], action_info[:http_verb].split('|').first ]
- end
- end
- nil
- end
- end
-end
diff --git a/lib/open_api/router.rb b/lib/open_api/router.rb
new file mode 100644
index 0000000..9ac5499
--- /dev/null
+++ b/lib/open_api/router.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module OpenApi
+ module Router
+ module_function
+
+ def routes
+ @routes ||=
+ if (file = Config.rails_routes_file)
+ File.read(file)
+ else
+ # :nocov:
+ # ref https://github.com/rails/rails/blob/master/railties/lib/rails/tasks/routes.rake
+ require './config/routes'
+ all_routes = Rails.application.routes.routes
+ require 'action_dispatch/routing/inspector'
+ inspector = ActionDispatch::Routing::RoutesInspector.new(all_routes)
+ inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, nil)
+ # :nocov:
+ end
+ end
+
+ def routes_list
+ @routes_list ||= routes.split("\n").drop(1).map do |line|
+ next unless line['#']
+ infos = line.match(/[A-Z|].*/).to_s.split(' ') # => [GET, /api/v1/examples/:id, api/v1/examples#index]
+
+ {
+ http_verb: infos[0].downcase, # => "get" / "get|post"
+ path: infos[1][0..-11].split('/').map do |item|
+ item[':'] ? "{#{item[1..-1]}}" : item
+ end.join('/'), # => "/api/v1/examples/{id}"
+ action_path: infos[2] # => "api/v1/examples#index"
+ } rescue next
+ end.compact.group_by { |api| api[:action_path].split('#').first } # => { "api/v1/examples" => [..] }, group by paths
+ end
+
+ def get_actions_by_route_base(route_base)
+ routes_list[route_base]&.map { |action_info| action_info[:action_path].split('#').last }
+ end
+
+ def find_path_httpverb_by(route_base, action)
+ routes_list[route_base]&.map do |action_info|
+ if action_info[:action_path].split('#').last == action.to_s
+ return [ action_info[:path], action_info[:http_verb].split('|').first ]
+ end
+ end ; nil
+ end
+ end
+end
diff --git a/lib/open_api/support/tip.rb b/lib/open_api/support/tip.rb
new file mode 100644
index 0000000..63ad937
--- /dev/null
+++ b/lib/open_api/support/tip.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module OpenApi
+ module Tip
+ extend self
+
+ def no_config; puts ' OpenApi'.red + ' No documents have been configured!' end
+ def loaded; puts ' OpenApi'.green + ' loaded' if ENV['RAILS_ENV'] end
+
+ def generated(name)
+ puts ' OpenApi'.green + " `#{name}.json` has been generated."
+ end
+
+ def schema_no_type(component_key)
+ puts ' OpenApi'.red + " Syntax Error: component schema `#{component_key}` has no type!"
+ end
+
+ def param_no_type(name)
+ puts ' OpenApi'.red + " Syntax Error: param `#{name}` has no schema type!"
+ end
+
+ def no_route(action_path)
+ puts ' OpenApi'.red + " Route mapping failed: #{action_path}"
+ end
+ end
+end
diff --git a/lib/open_api/version.rb b/lib/open_api/version.rb
index 4fb21eb..01938b9 100644
--- a/lib/open_api/version.rb
+++ b/lib/open_api/version.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
module OpenApi
- VERSION = '1.7.0'
+ VERSION = '2.0.0'
end
diff --git a/spec/api_spec.rb b/spec/api_spec.rb
index 12711e2..055ecf8 100644
--- a/spec/api_spec.rb
+++ b/spec/api_spec.rb
@@ -21,20 +21,6 @@
desc :desc do
api -> { desc 'description for api #action.' }, has_key: :description
-
- context "when uniting parameters' description" do
- let(:params) { subject[:parameters] }
-
- before_dsl! do
- desc '#action', name: 'name', age!: 'age', id: 'id'
- query :name, String
- query :age, Integer
- query :id, Integer, desc: 'override'
- end
- it { expect(params[0]).to include name: :name, description: 'name' }
- it { expect(params[1]).to include name: :age, description: 'age' }
- it { expect(params[2]).to include name: :id, description: 'override' }
- end
end
@@ -52,15 +38,15 @@
end
}
- make -> { api :action, use: [ ] }, 'uses all', has_size: 2
- make -> { api :action, use: [:none] }, then_it('only uses :none') { be_nil }
- make -> { api :action, use: [:page] }, has_size: 1
- make -> { api :action, skip: [ ] }, 'skips nothing', has_size: 2
- make -> { api :action, skip: [:page] }, has_size: 1
+ make -> { api(:action) { dry } }, 'uses all', has_size: 2
+ make -> { api(:action) { dry none: true } }, then_it('only uses :none') { be_nil }
+ make -> { api(:action) { dry only: [:page] } }, has_size: 1
+ make -> { api(:action) { dry } }, 'skips nothing', has_size: 2
+ make -> { api(:action) { dry skip: [:page] } }, has_size: 1
- make -> { api(:action, use: [:nothing]) { param :query, :page, Integer, :req } },
+ make -> { api(:action) { dry none: true; param :query, :page, Integer, :req } },
'not skip the params inside block', has_size: 1
- make -> { api(:action, skip: [:per]) { param :query, :per, Integer, :req } },
+ make -> { api(:action) { dry skip: [:per]; param :query, :per, Integer, :req } },
'not skip the params inside block', has_size: 2
after_do { undo_dry }
@@ -94,23 +80,15 @@
expect_it eq: 'integer'
end
- describe '#do_*:' do
- api -> { do_query by: { } }, then_it { be_nil }
-
- api -> { do_header by: { key: Integer, token!: String } }, has_size!: 2
+ describe '#in_*:' do
+ api -> { in_header(key: Integer, token!: String) }, has_size!: 2
it { expect(item_0).to include name: :key, required: false }
it { expect(item_1).to include name: :token, required: true }
context 'when calling bang method' do
- api -> { do_path! by: { id: Integer, name: String } }, '---> has 2 required items:', has_size!: 2
+ api -> { in_path!(id: Integer, name: { type: String }) }, '---> has 2 required items:', has_size!: 2
it { expect(item_0).to include name: :id, required: true}
- it { expect(item_1).to include name: :name, required: true}
- end
-
- context 'when passing common schema' do
- api -> { do_query by: { id: Integer, name: String }, pmt: true }, '---> has 2 required items:', has_size!: 2
- it { expect(item_0[:schema]).to include permit: true }
- it { expect(item_1[:schema]).to include permit: true }
+ it { expect(item_1).to include name: :name, required: true, schema: { type: 'string' }}
end
end
@@ -186,19 +164,6 @@
end
end
end
-
- describe '#file and #file!' do
- api -> { file :ppt }, has_its_structure!
- focus_on :content
- expect_it has_key: :'application/vnd.ms-powerpoint'
-
- step_into :'application/vnd.ms-powerpoint', :schema, :format
- expect_it eq: OpenApi::Config.file_format
-
- context 'when calling the bang method' do
- api -> { file! :doc }, include: { required: true }
- end
- end
end
describe '#body_ref' do
@@ -289,40 +254,40 @@
end
- desc :order, subject: :parameters do
- context 'when using in .api' do
- api -> do
- query :page, String
- path :id, Integer
- order :id, :page
- end, has_size!: 2
- it { expect(item_0).to include name: :id }
- it { expect(item_1).to include name: :page }
- end
-
- context 'when using in .api_dry' do
- before_do! do
- api_dry do
- header :token, String
- path :id, Integer
- order :id, :name, :age, :token, :remarks
- end
-
- api :action do
- query :remarks, String
- query :name, String
- query :age, String
- end
-
- undo_dry
- end
-
- focus_on :subject, desc: '`order` will auto generate `use` and `skip`, so:'
- expect_it { have_size 5 }
- expect_its(0) { include name: :id }
- expect_its(4) { include name: :remarks }
- end
- end
+ # desc :order, subject: :parameters do
+ # context 'when using in .api' do
+ # api -> do
+ # query :page, String
+ # path :id, Integer
+ # order :id, :page
+ # end, has_size!: 2
+ # it { expect(item_0).to include name: :id }
+ # it { expect(item_1).to include name: :page }
+ # end
+ #
+ # context 'when using in .api_dry' do
+ # before_do! do
+ # api_dry do
+ # header :token, String
+ # path :id, Integer
+ # order :id, :name, :age, :token, :remarks
+ # end
+ #
+ # api :action do
+ # query :remarks, String
+ # query :name, String
+ # query :age, String
+ # end
+ #
+ # undo_dry
+ # end
+ #
+ # focus_on :subject, desc: '`order` will auto generate `use` and `skip`, so:'
+ # expect_it { have_size 5 }
+ # expect_its(0) { include name: :id }
+ # expect_its(4) { include name: :remarks }
+ # end
+ # end
desc :param_examples, subject: :examples do
diff --git a/spec/components_spec.rb b/spec/components_spec.rb
index 860d5a3..95a17ad 100644
--- a/spec/components_spec.rb
+++ b/spec/components_spec.rb
@@ -1,7 +1,7 @@
require 'spec_dsl'
def clear
- after_do { @doc_info[:components] = { } }
+ after_do { oas[:doc][:components] = { } }
end
RSpec.describe OpenApi::DSL::Components do
diff --git a/spec/dsl_spec.rb b/spec/dsl_spec.rb
index 46bbb63..10630bf 100644
--- a/spec/dsl_spec.rb
+++ b/spec/dsl_spec.rb
@@ -4,7 +4,7 @@
set_doc
desc :route_base, subject: :paths do
- before_do { route_base 'examples' }
+ before_do { @oas = nil; route_base 'examples' }
make -> { api :action }, 'is not mapped to goods#action', eq: Hash.new
make -> { api :index }, 'is mapped to examples#index', has_key!: :'examples/index'
@@ -14,22 +14,19 @@
after_do { route_base 'goods' }
end
-
desc :doc_tag do
make -> do
- doc_tag name: :Other, desc: 'tag desc', external_doc_url: 'url'
+ doc_tag name: :Other, description: 'tag desc'
api :action
end, has_keys!: %i[ tags paths ]
focus_on :tags, 0
expect_its :name, eq: :Other
expect_its :description, eq: 'tag desc'
- expect_its :externalDocs, eq: { description: 'ref', url: 'url' }
focus_on :paths, :'goods/action', :get, :tags, 0
expect_it eq: :Other
end
-
desc :api, subject: :paths do
context 'when this action is not configured routing' do
make -> { api :no_routing_action }, 'refuses to be generated', eq: Hash.new
@@ -50,18 +47,18 @@
desc :api_dry, subject: :paths do
context 'when using the default :all parameter' do
make -> do
- api_dry { resp :success, 'success response' }
- api :create
- api :index
+ api_dry { response :success, 'success response' }
+ api(:create) { dry }
+ api(:index) { dry }
end, 'makes all actions have a :success response',
has_keys: { goods: [ get: [responses: [:success]], post: [responses: [:success]] ] }
end
context 'when the action is specified' do
make -> do
- api_dry(:index) { resp :success, 'success response' }
+ api_dry(:index) { response :success, 'success response' }
api :create
- api :index
+ api(:index) { dry }
end, has_keys!: :goods
focus_on :goods, :get
expect_its :responses, has_keys: :success
diff --git a/spec/generator_spec.rb b/spec/generator_spec.rb
index 01f8c40..08e5842 100644
--- a/spec/generator_spec.rb
+++ b/spec/generator_spec.rb
@@ -1,8 +1,8 @@
require 'support/open_api'
-RSpec.describe OpenApi::Generator do
+RSpec.describe OpenApi::Router do
describe '.routes_list' do
- subject { OpenApi::Generator.routes_list }
+ subject { OpenApi::Router.routes_list }
it { is_expected.to have_keys 'goods' }
let(:goods_routes) { subject['goods'] }
diff --git a/spec/oas_objs/schema_obj_spec.rb b/spec/oas_objs/schema_obj_spec.rb
index 9ee7e6a..cc4ef5e 100644
--- a/spec/oas_objs/schema_obj_spec.rb
+++ b/spec/oas_objs/schema_obj_spec.rb
@@ -16,7 +16,7 @@
api -> { query :people, type: { name: String } }, include: { type: 'object' }
end
- desc :processed_type do
+ desc :recg_schema_type do
context 'when not be one of the [Hash, Array, Symbol]' do
context 'when in [ float double int32 int64 ]' do
api -> { query :info, Float }, get: { type: 'number', format: 'float' }
@@ -88,11 +88,11 @@
expect_its :properties, has_keys: %i[ first last ]
end
- context 'with key :type' do
+ context 'with key :type' do # ???
# OR: query :info, type: { type: String, desc: 'info' }
- api -> { query :info, { type: String, desc: 'info' }, desc: 'api desc' }, 'has description within schema',
- has_key!: :description
- it { expect(description).to eq 'info' } # not_to eq 'api desc'
+ # api -> { query :info, { type: String, desc: 'info' }, desc: 'api desc' }, 'has description within schema',
+ # has_key!: :description
+ # it { expect(description).to eq 'info' } # not_to eq 'api desc'
end
context 'when having keys in [ one_of any_of all_of not ]' do
@@ -125,13 +125,8 @@
it { expect(max_items).to eq 20 }
end
- context 'when enum is or not an array' do
+ context 'when enum is an array' do
api -> { query :info, String, enum: ['a'] }, include: { enum: ['a'] }
- api -> { query :info, String, enum: 'a' }, include: { enum: ['a'] }
- end
-
- context 'when using must_be (value)' do
- api -> { query :info, String, must_be: 'a' }, 'is also enum', include: { enum: ['a'] }
end
context 'when passing Range to lth' do
@@ -148,17 +143,12 @@
let(:description) { %i[ paths goods/action get parameters ].reduce(OpenApi.docs[:zro].deep_symbolize_keys, &:[])[0][:description] }
context 'when passing Array to enum!' do
- api -> { query :info, String, enum!: %w[ a b ], desc!: 'info: ' }, has_key!: :enum
+ api -> { query :info, String, enum!: %w[ a b ], desc: 'info: ' }, has_key!: :enum
it { expect(description).to eq 'info:
1/ a
2/ b' }
-
- context 'when not passing desc!' do
- api -> { query :info, String, enum!: %w[ a b ] }, has_key!: :enum
- it('parameter has not desc') { expect(description).to eq nil }
- end
end
context 'when passing Hash to enum!' do
- api -> { query :info, String, enum!: { 'desc1': :a, 'desc2': :b }, desc!: 'info: ' }, has_key!: :enum
+ api -> { query :info, String, enum!: { 'desc1': :a, 'desc2': :b }, desc: 'info: ' }, has_key!: :enum
it { expect(description).to eq 'info:
1/ desc1: a
2/ desc2: b' }
end
end
@@ -173,7 +163,7 @@
desc :is_and_format do
correct do
- api -> { query :email, Integer, is: :email }, include: { is: :email, format: :email }
+ api -> { query :email, Integer, is_a: :email }, include: { is_a: :email, format: :email }
end
end
@@ -190,7 +180,7 @@
end
describe ':default' do
- api -> { query :info, String, dft: 'default' }, include: { default: 'default' }
+ api -> { query :info, String, default: 'default' }, include: { default: 'default' }
end
describe ':example' do
@@ -198,7 +188,7 @@
end
describe ':examples' do
- api -> { query :info, { name: String, age: Integer }, examples: { input1: ['a', 1], input2: ['b, 2'] }, exp_by: %i[ name age ] },
+ api -> { query :info, { name: String, age: Integer }, examples: { input1: ['a', 1], input2: ['b, 2'] }, exp_params: %i[ name age ] },
has_key!: :examples
focus_on :examples
expect_its 0, eq: { input1: { value: { name: 'a', age: 1 } } }
@@ -212,4 +202,10 @@
api -> { query :info, '{=>integer}' }, include: { additionalProperties: { type: 'integer' } }
end
end
+
+ desc :custom_addition do
+ correct do
+ api -> { query :info, Integer, permit: true }, include: { permit: true }
+ end
+ end
end
diff --git a/spec/spec_dsl.rb b/spec/spec_dsl.rb
index f007ac5..fa04dc5 100644
--- a/spec/spec_dsl.rb
+++ b/spec/spec_dsl.rb
@@ -23,8 +23,8 @@ def _dsl(block)
end
def _write_docs
- OpenApi.write_docs(generate_files: false)
- GoodsDoc.class_eval { undo_dry; @api_info = { } }
+ OpenApi.write_docs(if: false)
+ GoodsDoc.class_eval { undo_dry; oas[:apis] = { } }
end
def _do!(block)
diff --git a/spec/support/api_doc.rb b/spec/support/api_doc.rb
index 850561d..3fa57b3 100644
--- a/spec/support/api_doc.rb
+++ b/spec/support/api_doc.rb
@@ -3,7 +3,7 @@ class ApiDoc < Object
class << self
def undo_dry
- @zro_dry_blocks = nil
+ oas[:dry_blocks] = { }
end
def inherited(subclass)
diff --git a/zero-rails_openapi.gemspec b/zero-rails_openapi.gemspec
index d6ed7bf..8f02d40 100644
--- a/zero-rails_openapi.gemspec
+++ b/zero-rails_openapi.gemspec
@@ -23,15 +23,15 @@ Gem::Specification.new do |spec|
spec.required_ruby_version = '>= 2.3.0'
- spec.add_development_dependency 'bundler', '~> 1.16.a'
- spec.add_development_dependency 'rake', '~> 10.0'
- spec.add_development_dependency 'rspec', '~> 3.0'
+ spec.add_development_dependency 'bundler'
+ spec.add_development_dependency 'rake'
+ spec.add_development_dependency 'rspec'
spec.add_development_dependency 'simplecov'
spec.add_development_dependency 'pry'
- spec.add_runtime_dependency 'colorize'
- spec.add_runtime_dependency 'activesupport', '>= 4.1'
- spec.add_runtime_dependency 'rails', '>= 4.1'
+ spec.add_dependency 'colorize'
+ spec.add_dependency 'activesupport', '>= 4.1'
+ spec.add_dependency 'rails', '>= 4.1'
# spec.post_install_message = ""
end