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