This article is the 12th day article of CBcloud Advent Calendar 2020.
I will introduce what I was addicted to when trying to actually generate an OpenAPI document in business and share it with members with Rails + Grape + Grape Swagger.
OpenAPI It is described as follows in the OpenAPI repository. https://github.com/OAI/OpenAPI-Specification
The OpenAPI Specification (OAS) defines a standard, programming language-agnostic interface description for HTTP APIs, which allows both humans and computers to discover and understand the capabilities of a service without requiring access to source code, additional documentation, or inspection of network traffic.
In other words, OpenAPI is a specification for describing the interface of HTTP API that does not depend on the programming language. Documents written according to the specification can be read and understood by humans, and can be analyzed to provide a nice UI.
Swagger The Swagger page says: https://swagger.io/about/
Swagger is a powerful yet easy-to-use suite of API developer tools for teams and individuals, enabling development across the entire API lifecycle, from design and documentation, to test and deployment. ~ Abbreviation ~ Swagger started out as a simple, open source specification for designing RESTful APIs in 2010. Open source tooling like the Swagger UI, Swagger Editor and the Swagger Codegen were also developed to better implement and visualize APIs defined in the specification. ~ Abbreviation ~ In 2015, the Swagger project was acquired by SmartBear Software. The Swagger Specification was donated to the Linux foundation and renamed the OpenAPI
In other words, Swagger is a project that provides a set of tools for API developers. And the specification for Swagger's API definition was donated to the Linux foundation and renamed to OpenAPI.
Based on the above, what I'm trying to do (and I'm addicted to) in this article is a project that is developing with a Rails + Grape configuration, and uses Grape Swagger's DSL to generate JSON that conforms to the OpenAPI/Swagger specification. Is to do. Also, when I read the generated JSON and see it with a tool that makes it easy to see, I aim to display it as the intended configuration (and I am addicted to it)
The gems used to generate the document are as follows.
gem 'grape' #A framework with a DSL for developing RESTful APIs
gem 'grape-swagger' #Document generation from Grape API
gem 'grape-entity' #Add response formatting tools to the Grape framework
gem 'grape-swagger-entity' # grape-Document generation from entity
Please refer to each repository for setting and usage. https://github.com/ruby-grape/grape https://github.com/ruby-grape/grape-swagger https://github.com/ruby-grape/grape-entity https://github.com/ruby-grape/grape-swagger-entity
When using Grape, you may write something like the following.
This means that user_name is a required parameter, and if you actually empty the request body and make a request, it will return with a 400 error as user_name is missing
.
class Users < Grape::API
resources :params_in_same_layer do
desc 'When params are written in the same hierarchy as desc'
params do
requires :user_name, type: String, documentation: { desc: 'User name', type: 'string' }
optional :address, type: String, documentation: { desc: 'Delivery information', type: 'string' }
end
post do
present hoge: 'fuga'
end
end
end
Next, when you try to document with Grape Swagger, the following description method will appear. If you make a request without a request body as before, you will get 201 instead of 400 error this time.
class Users < Grape::API
resources :params_whitin_desc_block do
desc 'When params are written only in the desc block' do
params SimpleUserParamsEntity.documentation
end
post do
present hoge: 'fuga'
end
end
end
class SimpleUserParamsEntity < Grape::Entity
expose :user_name, documentation: { desc: 'Username (defined by entity)', type: 'string', required: true }
expose :address, documentation: { desc: 'Address (defined by entity)', type: 'string' }
end
Let's compare the difference when looking at the above in Swagger UI. In both cases, it can be expressed that the user name is required. But be aware that the second endpoint isn't really required.
So what if you write in both? I also added the keys age and blood_type, which are defined only for each.
class Users < Grape::API
resources :params_in_same_layer_and_desc_block do
desc 'When params are written both in the desc block and in the same hierarchy of desc' do
params SimpleUserParamsEntity.documentation
end
params do
requires :user_name, type: String, documentation: { desc: 'User name', type: 'string' }
optional :address, type: String, documentation: { desc: 'Delivery information', type: 'string' }
optional :age, type: Integer, documentation: { desc: 'age', type: 'string' }
end
post do
present hoge: 'fuga'
end
end
end
class SimpleUserParamsEntity < Grape::Entity
expose :user_name, documentation: { desc: 'Username (defined by entity)', type: 'string', required: true }
expose :address, documentation: { desc: 'Address (defined by entity)', type: 'string' }
expose :blood_type, documentation: { desc: 'Blood type (defined by entity)', type: 'string' }
end
Looking at the Swagger UI, it looks like this. If the keys match, the documentation in the desc takes precedence, otherwise it seems to be output respectively.
Let's see what happens to the UI with a complex parameter pattern. Also, prepare an endpoint written in the same hierarchy as when it is written in the desc block.
class Users < Grape::API
resources :complex_params_in_same_layer do
desc 'When params are written in the same hierarchy as desc'
params do
requires :user_name, type: Integer, documentation: { desc: 'User name', type: 'string' }
optional :addresses, type: Array[JSON], documentation: { desc: 'Delivery information', type: 'array', collectionFormat: 'multi' } do
requires :name, type: String, documentation: { desc: 'Delivery name', type: 'string' }
requires :address, type: String, documentation: { desc: 'Delivery address', type: 'string' }
requires :tags, type: Array[JSON], documentation: { desc: 'tag', type: 'array', collectionFormat: 'multi' } do
optional :name, type: String, documentation: { desc: 'Tag name', type: 'string'}
end
end
end
post do
present hoge: 'fuga'
end
end
resources :complex_params_in_desc_block do
desc 'When params are written only in the desc block' do
params ComplexUserParamsEntity.documentation
end
post do
present hoge: 'fuga'
end
end
end
class ComplexUserParamsEntity < Grape::Entity
class TagEntity < Grape::Entity
expose :name, documentation: { desc: 'Tag name (defined by entity)', type: 'string' }
end
class AddressEntity < Grape::Entity
expose :name, documentation: { desc: 'Delivery name (defined by entity)', type: 'string' }
expose :address, documentation: { desc: 'Delivery address (defined by entity)', type: 'string' }
expose :tags, documentation: { desc: 'Tag (defined by entity)', type: 'array', is_array: true }, using: TagEntity
end
expose :user_name, documentation: { desc: 'Username (defined by entity)', type: 'string' }
expose :addresses, documentation: { desc: 'Delivery information (defined by entity)', type: 'array', is_array: true }, using: AddressEntity
end
Let's check it with Swagger UI. One is not displayed in the array, the other is displayed, but the hierarchical structure is difficult to understand.
I will try to make it easier to see. Try adding param_type:'body' to all parameters.
class Users < Grape::API
resources :complex_params_in_same_layer do
desc 'When params are written in the same hierarchy as desc'
params do
requires :user_name, type: Integer, documentation: { desc: 'User name', type: 'string', param_type: 'body' }
#The following is omitted
Check again in the Swagger UI. Then, the one defined by Entity still loses the hierarchical structure. The way of writing Entity may be bad. And on the other hand, the hierarchy is easier to understand, but it doesn't reflect the explanation.
So, I will try a UI that can interpret other OpenAPI. Click here to use. https://github.com/Redocly/redoc
Entity is still the same, but the explanation is a little better. And for those on the same level, the display is exactly what you want! !! (The explanation "Delivery information" attached to the key of the array is also displayed properly)
What you can see from this is that the actual display depends on the UI tool you use, so let's make sure that the tool you use will display it properly.
Even if I specify example, it is not output, so I wondered why there were the following issues. Since it is open at the moment, it seems that it can be done by including the workaround posted in issues until it is resolved. https://github.com/ruby-grape/grape-swagger/issues/762
I still don't understand how to use the entity in the request parameter, so I would appreciate it if you could point out any mistakes.
Recommended Posts