zero-rails_openapi 1.3.2 → 1.3.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +0 -2
- data/Gemfile.lock +90 -3
- data/README.md +409 -357
- data/documentation/examples/examples_controller.rb +2 -2
- data/documentation/examples/goods_doc.rb +14 -12
- data/documentation/examples/open_api.rb +14 -4
- data/documentation/examples/{example_output_doc.json → output_example.json} +0 -0
- data/documentation/parameter.md +17 -12
- data/lib/oas_objs/schema_obj.rb +1 -1
- data/lib/open_api/config.rb +18 -18
- data/lib/open_api/config_dsl.rb +21 -2
- data/lib/open_api/dsl.rb +12 -13
- data/lib/open_api/dsl/api_info_obj.rb +27 -2
- data/lib/open_api/dsl/common_dsl.rb +1 -0
- data/lib/open_api/dsl/ctrl_info_obj.rb +1 -1
- data/lib/open_api/dsl/helpers.rb +4 -4
- data/lib/open_api/generator.rb +16 -5
- data/lib/open_api/version.rb +1 -1
- data/zero-rails_openapi.gemspec +3 -9
- metadata +31 -3
@@ -23,9 +23,9 @@ class Api::V1::ExamplesController < Api::V1::BaseController
|
|
23
23
|
desc 'Optional multiline or single-line Markdown-formatted description',
|
24
24
|
id: 'user id',
|
25
25
|
email_addr: 'email_addr\'s desc'
|
26
|
-
email = '
|
26
|
+
email = 'zero@rails.org'
|
27
27
|
|
28
|
-
query! :id, Integer, enum: 0..5, length: [1, 2], pattern: /^[0-9]$/, range: {gt:0, le:5}
|
28
|
+
query! :id, Integer, enum: 0..5, length: [1, 2], pattern: /^[0-9]$/, range: { gt:0, le:5 }
|
29
29
|
query! :done, Boolean, must_be: false, default: true, desc: 'must be false'
|
30
30
|
query :email_addr, String, lth: :ge_3, default: email # is_a: :email
|
31
31
|
|
@@ -1,12 +1,13 @@
|
|
1
1
|
class V2::GoodsDoc < BaseDoc
|
2
2
|
|
3
|
-
open_api :index, '
|
4
|
-
use: [ 'Token' ] do # use parameters write in AutoGenDoc#api_dry
|
5
|
-
# skip:
|
3
|
+
open_api :index, 'GET list of Goods.', builder: :index, # jbuilder templates is set in initializers/open_api.rb
|
4
|
+
use: [ 'Token', :page, :rows ] do # use parameters write in AutoGenDoc#api_dry
|
5
|
+
# skip: [ 'Token' ] do # you can also skip parameters
|
6
6
|
desc 'listing Goods',
|
7
7
|
view!: 'search view, allows::<br/>',
|
8
8
|
search_type!: 'search field, allows:<br/>'
|
9
9
|
|
10
|
+
# Single `query`
|
10
11
|
query :view, String, enum: {
|
11
12
|
'all goods (default)': :all,
|
12
13
|
'only online': :online,
|
@@ -14,22 +15,23 @@ class V2::GoodsDoc < BaseDoc
|
|
14
15
|
'expensive goods': :expensive,
|
15
16
|
'cheap goods': :cheap,
|
16
17
|
}
|
17
|
-
# query
|
18
|
+
# Batch `query`
|
18
19
|
do_query by: {
|
19
20
|
:search_type => { type: String, enum: %w[ name creator category price ] },
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
21
|
+
:value => String,
|
22
|
+
:export => { type: Boolean, desc: 'export as Excel format', examples: {
|
23
|
+
:right_input => true,
|
24
|
+
:wrong_input => 'wrong input'
|
25
|
+
}}
|
24
26
|
}
|
25
27
|
end
|
26
28
|
|
27
29
|
|
28
|
-
open_api :create, '
|
30
|
+
open_api :create, 'POST create a Good', builder: :success_or_not, use: 'Token' do
|
29
31
|
form! 'for creating a good', data: {
|
30
32
|
:name! => { type: String, desc: 'good\'s name' },
|
31
33
|
:category_id! => { type: Integer, desc: 'sub_category\'s id', npmt: true, range: { ge: 1 }, as: :cate },
|
32
|
-
:price! => { type: Float, desc: 'good\'s price', range: { ge: 0} },
|
34
|
+
:price! => { type: Float, desc: 'good\'s price', range: { ge: 0 } },
|
33
35
|
# -- optional
|
34
36
|
:is_online => { type: Boolean, desc: 'it\'s online?' },
|
35
37
|
:remarks => { type: String, desc: 'remarks' },
|
@@ -43,8 +45,8 @@ class V2::GoodsDoc < BaseDoc
|
|
43
45
|
end
|
44
46
|
|
45
47
|
|
46
|
-
open_api :show, '
|
48
|
+
open_api :show, 'GET a Good.', builder: :show, use: [ 'Token', :id ]
|
47
49
|
|
48
50
|
|
49
|
-
open_api :destroy, '
|
51
|
+
open_api :destroy, 'DELETE a Good.', builder: :success_or_not, use: [ 'Token', :id ]
|
50
52
|
end
|
@@ -1,6 +1,16 @@
|
|
1
1
|
require 'open_api'
|
2
2
|
|
3
3
|
OpenApi::Config.tap do |c|
|
4
|
+
# Config DSL
|
5
|
+
c.instance_eval do
|
6
|
+
api :zero_rails_api, root_controller: ApiDoc
|
7
|
+
info version: '0.0.1', title: 'Zero Rails APIs', description: 'API documentation of Zero-Rails Application.'
|
8
|
+
server 'http://localhost:3000', desc: 'Main (production) server'
|
9
|
+
server 'http://localhost:3000', desc: 'Internal staging server for testing'
|
10
|
+
security ApiKeyAuth: [ ]
|
11
|
+
security_scheme :ApiKeyAuth, type: 'apiKey', name: 'server_token', in: 'query'
|
12
|
+
end
|
13
|
+
|
4
14
|
# [REQUIRED] The location where .json doc file will be output.
|
5
15
|
c.file_output_path = 'public/open_api'
|
6
16
|
|
@@ -88,7 +98,7 @@ OpenApi::Config.tap do |c|
|
|
88
98
|
c.jbuilder_templates = {
|
89
99
|
index: (
|
90
100
|
<<~FILE
|
91
|
-
# *** Generated by ZRO ***
|
101
|
+
# *** Generated by ZRO [ please make sure that you have checked this file ] ***
|
92
102
|
json.partial! 'api/base', total: @data.size
|
93
103
|
|
94
104
|
json.data do
|
@@ -103,7 +113,7 @@ OpenApi::Config.tap do |c|
|
|
103
113
|
|
104
114
|
show: (
|
105
115
|
<<~FILE
|
106
|
-
# *** Generated by ZRO ***
|
116
|
+
# *** Generated by ZRO [ please make sure that you have checked this file ] ***
|
107
117
|
json.partial! 'api/base', total: 1
|
108
118
|
|
109
119
|
json.data do
|
@@ -116,14 +126,14 @@ OpenApi::Config.tap do |c|
|
|
116
126
|
|
117
127
|
success: (
|
118
128
|
<<~FILE
|
119
|
-
# *** Generated by ZRO ***
|
129
|
+
# *** Generated by ZRO [ please make sure that you have checked this file ] ***
|
120
130
|
json.partial! 'api/success'
|
121
131
|
FILE
|
122
132
|
),
|
123
133
|
|
124
134
|
success_or_not: (
|
125
135
|
<<~FILE
|
126
|
-
# *** Generated by ZRO ***
|
136
|
+
# *** Generated by ZRO [ please make sure that you have checked this file ] ***
|
127
137
|
unless @status
|
128
138
|
# @_code, @_msg = @error_info.present? ? @error_info : ApiError.action_failed.info
|
129
139
|
end
|
File without changes
|
data/documentation/parameter.md
CHANGED
@@ -1,21 +1,26 @@
|
|
1
|
-
### More Explanation
|
1
|
+
### More Explanation for `param` and `schema_hash`
|
2
2
|
|
3
|
-
#### param_type
|
3
|
+
#### param_type (param_location)
|
4
4
|
OpenAPI 3.0 distinguishes between the following parameter types based on the parameter location:
|
5
5
|
**header, path, query, cookie**. [more](https://swagger.io/docs/specification/describing-parameters/)
|
6
6
|
|
7
|
-
#### name
|
8
|
-
|
9
|
-
|
7
|
+
#### name (param_name)
|
8
|
+
The name of parameter. It can be Symbol or String.
|
9
|
+
|
10
|
+
If param_type is :path, it must correspond to the associated path segment form
|
11
|
+
the routing path, for example: if the API path is `/good/:id`, you have to declare a path parameter with name `id` to it.
|
12
|
+
|
13
|
+
#### type (schema_type)
|
14
|
+
Parameter's (schema) type. We call it `schema_type` because it is inside SchemaObj.
|
15
|
+
|
16
|
+
Support all [data types](https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.0.md#dataTypes) defined in OAS.
|
10
17
|
|
11
|
-
#### type
|
12
|
-
parameter (schema) type. Support all [data types](https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.0.md#dataTypes) defined in OAS.
|
13
18
|
In addition, you can use `format` in schema_hash to define in fine detail the data type being used, like:
|
14
19
|
int32, float, date ...
|
15
|
-
All the types you can use
|
20
|
+
All the types you can use as following:
|
16
21
|
- **String, 'binary', 'base64'**
|
17
22
|
- **Integer, Long, 'int32', 'int64', Float, Double**
|
18
|
-
- **File** (it will be converted
|
23
|
+
- **File** (it will be converted to `{ type: 'string', format: Config.dft_file_format }`)
|
19
24
|
- **Date, DateTime**
|
20
25
|
- **Boolean**
|
21
26
|
- **Array**: `Array[String]` or `[String]`
|
@@ -24,8 +29,8 @@ All the types you can use are:
|
|
24
29
|
(`!` bang key means it is required).
|
25
30
|
- Nested Object: `{ id!: Integer, name: { first: String, last: String } }`
|
26
31
|
- Nested Array and Object: `[[{ id!: Integer, name: { first: String, last: String } }]]`
|
27
|
-
- **:ComponentKey**:
|
28
|
-
to the component correspond to ComponentKey
|
32
|
+
- **:ComponentKey**: pass **Symbol** value to type will generate a Schema Reference Object link
|
33
|
+
to the component correspond to ComponentKey, like: :IdPath, :NameQuery
|
29
34
|
|
30
35
|
You can use `Object.const_set()` to define a constant that does not exist, but note that
|
31
36
|
the value you set could not be a Symbol (it will be explained as a Ref Object), should be a String.
|
@@ -59,4 +64,4 @@ You can set the schema by following keys (all are optional), the words in parent
|
|
59
64
|
5. If type is Object, for describing each property's schema, the only way is use ref type, like: `{ id: :Id, name: :Name }`
|
60
65
|
- **pattern (regexp, pr, reg)**
|
61
66
|
- **default (dft, default_value)**
|
62
|
-
- **as** # TODO
|
67
|
+
- **as** # TODO
|
data/lib/oas_objs/schema_obj.rb
CHANGED
@@ -33,7 +33,7 @@ module OpenApi
|
|
33
33
|
processed_is_and_format(param_name),
|
34
34
|
{
|
35
35
|
pattern: _pattern&.inspect&.delete('/'),
|
36
|
-
default: '_default',
|
36
|
+
default: _default.nil? ? nil : '_default',
|
37
37
|
examples: self[:examples].present? ? ExampleObj.new(self[:examples], self[:exp_by]).process : nil,
|
38
38
|
},
|
39
39
|
{ as: _as, permit: _permit, not_permit: _npermit, req_if: _req_if, opt_if: _opt_if }
|
data/lib/open_api/config.rb
CHANGED
@@ -18,20 +18,20 @@ module OpenApi
|
|
18
18
|
# Getting started: https://swagger.io/docs/specification/basic-structure/
|
19
19
|
cattr_accessor :register_docs do
|
20
20
|
{
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
21
|
+
# # [REQUIRED] At least one doc.
|
22
|
+
# zero_rails_api: {
|
23
|
+
# # [REQUIRED] ZRO will scan all the descendants of the root_controller, and then generate their docs.
|
24
|
+
# root_controller: ApplicationController,
|
25
|
+
#
|
26
|
+
# # [REQUIRED] Info Object: The info section contains API information
|
27
|
+
# info: {
|
28
|
+
# # [REQUIRED] The title of the application.
|
29
|
+
# title: 'Zero Rails Apis',
|
30
|
+
# # [REQUIRED] The version of the OpenAPI document
|
31
|
+
# # (which is distinct from the OpenAPI Specification version or the API implementation version).
|
32
|
+
# version: '0.0.1'
|
33
|
+
# }
|
34
|
+
# }
|
35
35
|
}
|
36
36
|
end
|
37
37
|
|
@@ -55,7 +55,7 @@ module OpenApi
|
|
55
55
|
{
|
56
56
|
index: (
|
57
57
|
<<~FILE
|
58
|
-
# *** Generated by ZRO ***
|
58
|
+
# *** Generated by ZRO [ please make sure that you have checked this file ] ***
|
59
59
|
json.partial! 'api/base', total: @data.size
|
60
60
|
|
61
61
|
json.data do
|
@@ -70,7 +70,7 @@ module OpenApi
|
|
70
70
|
|
71
71
|
show: (
|
72
72
|
<<~FILE
|
73
|
-
# *** Generated by ZRO ***
|
73
|
+
# *** Generated by ZRO [ please make sure that you have checked this file ] ***
|
74
74
|
json.partial! 'api/base', total: 1
|
75
75
|
|
76
76
|
json.data do
|
@@ -83,14 +83,14 @@ module OpenApi
|
|
83
83
|
|
84
84
|
success: (
|
85
85
|
<<~FILE
|
86
|
-
# *** Generated by ZRO ***
|
86
|
+
# *** Generated by ZRO [ please make sure that you have checked this file ] ***
|
87
87
|
json.partial! 'api/success'
|
88
88
|
FILE
|
89
89
|
),
|
90
90
|
|
91
91
|
success_or_not: (
|
92
92
|
<<~FILE
|
93
|
-
# *** Generated by ZRO ***
|
93
|
+
# *** Generated by ZRO [ please make sure that you have checked this file ] ***
|
94
94
|
unless @status
|
95
95
|
# @_code, @_msg = @error_info.present? ? @error_info : ApiError.action_failed.info
|
96
96
|
end
|
data/lib/open_api/config_dsl.rb
CHANGED
@@ -4,8 +4,27 @@ module OpenApi
|
|
4
4
|
base.class_eval do
|
5
5
|
module_function
|
6
6
|
|
7
|
-
def
|
8
|
-
|
7
|
+
def api name, root_controller:
|
8
|
+
@api = name
|
9
|
+
register_docs[name] = { root_controller: root_controller }
|
10
|
+
end
|
11
|
+
|
12
|
+
def info version:, title:, **addition
|
13
|
+
register_docs[@api].merge! version: version, title: title, **addition
|
14
|
+
end
|
15
|
+
|
16
|
+
def server url, desc: ''
|
17
|
+
(register_docs[@api][:servers] ||= [ ]) << { url: url, description: desc }
|
18
|
+
end
|
19
|
+
|
20
|
+
def security requirement
|
21
|
+
(register_docs[@api][:global_security] ||= [ ]) << requirement
|
22
|
+
end
|
23
|
+
|
24
|
+
alias_method :security_require, :security
|
25
|
+
|
26
|
+
def security_scheme scheme_name, schema# = { }
|
27
|
+
(register_docs[@api][:global_security_schemes] ||= { }).merge! scheme_name => schema
|
9
28
|
end
|
10
29
|
end
|
11
30
|
end
|
data/lib/open_api/dsl.rb
CHANGED
@@ -26,28 +26,28 @@ module OpenApi
|
|
26
26
|
def components &block
|
27
27
|
apis_tag if @_ctrl_infos.nil?
|
28
28
|
current_ctrl = @_ctrl_infos[:components] = CtrlInfoObj.new
|
29
|
-
current_ctrl.instance_eval
|
29
|
+
current_ctrl.instance_eval(&block)
|
30
30
|
current_ctrl._process_objs
|
31
31
|
end
|
32
32
|
|
33
|
-
def open_api
|
33
|
+
def open_api action, summary = '', builder: nil, skip: [ ], use: [ ], &block
|
34
34
|
apis_tag if @_ctrl_infos.nil?
|
35
35
|
|
36
|
-
# select the routing info (corresponding to the current method) from
|
37
|
-
action_path = "#{@_ctrl_path ||= controller_path}##{
|
36
|
+
# select the routing info (corresponding to the current method) from routing list.
|
37
|
+
action_path = "#{@_ctrl_path ||= controller_path}##{action}"
|
38
38
|
routes_info = ctrl_routes_list&.select { |api| api[:action_path].match? /^#{action_path}$/ }&.first
|
39
|
-
pp "[ZRO Warning] Routing mapping failed: #{@_ctrl_path}##{
|
39
|
+
pp "[ZRO Warning] Routing mapping failed: #{@_ctrl_path}##{action}" and return if routes_info.nil?
|
40
40
|
Generator.generate_builder_file(action_path, builder) if builder.present?
|
41
41
|
|
42
42
|
# structural { #path: { #http_method:{ } } }, for pushing into Paths Object.
|
43
43
|
path = (@_api_infos ||= { })[routes_info[:path]] ||= { }
|
44
44
|
current_api = path[routes_info[:http_verb]] =
|
45
45
|
ApiInfoObj.new(action_path, skip: Array(skip), use: Array(use))
|
46
|
-
.merge! description: '', summary: summary, operationId:
|
46
|
+
.merge! description: '', summary: summary, operationId: action, tags: [@_apis_tag],
|
47
47
|
parameters: [ ], requestBody: '', responses: { }, security: [ ], servers: [ ]
|
48
48
|
|
49
49
|
current_api.tap do |api|
|
50
|
-
[
|
50
|
+
[action, :all].each do |key| # blocks_store_key
|
51
51
|
@_apis_blocks&.[](key)&.each { |blk| api.instance_eval(&blk) }
|
52
52
|
end
|
53
53
|
api.param_use = [ ] # skip 和 use 是对 dry 块而言的
|
@@ -58,18 +58,17 @@ module OpenApi
|
|
58
58
|
end
|
59
59
|
|
60
60
|
# method could be symbol array, like: %i[ .. ]
|
61
|
-
def api_dry
|
61
|
+
def api_dry action = :all, desc = '', &block
|
62
62
|
@_apis_blocks ||= { }
|
63
|
-
if
|
64
|
-
|
63
|
+
if action.is_a? Array
|
64
|
+
action.each { |m| (@_apis_blocks[m.to_sym] ||= [ ]) << block }
|
65
65
|
else
|
66
|
-
(@_apis_blocks[
|
66
|
+
(@_apis_blocks[action.to_sym] ||= [ ]) << block
|
67
67
|
end
|
68
68
|
end
|
69
69
|
|
70
70
|
def ctrl_routes_list
|
71
|
-
|
72
|
-
@routes_list[@_ctrl_path]
|
71
|
+
Generator.routes_list[@_ctrl_path]
|
73
72
|
end
|
74
73
|
end
|
75
74
|
end
|
@@ -6,7 +6,7 @@ module OpenApi
|
|
6
6
|
include DSL::CommonDSL
|
7
7
|
include DSL::Helpers
|
8
8
|
|
9
|
-
attr_accessor :action_path, :param_skip, :param_use, :param_descs
|
9
|
+
attr_accessor :action_path, :param_skip, :param_use, :param_descs, :param_order
|
10
10
|
|
11
11
|
def initialize(action_path, skip: [ ], use: [ ])
|
12
12
|
self.action_path = action_path
|
@@ -50,7 +50,7 @@ module OpenApi
|
|
50
50
|
%i[header header! path path! query query! cookie cookie!].each do |param_type|
|
51
51
|
define_method "do_#{param_type}" do |by:|
|
52
52
|
by.each do |key, value|
|
53
|
-
args = [ key.dup.to_s.delete('!'), value.delete(:type), value ]
|
53
|
+
args = [ key.dup.to_s.delete('!').to_sym, value.delete(:type), value ]
|
54
54
|
key.to_s['!'] ? send("#{param_type}!", *args) : send(param_type, *args)
|
55
55
|
end
|
56
56
|
end unless param_type.to_s['!']
|
@@ -113,12 +113,37 @@ module OpenApi
|
|
113
113
|
self[:servers] << { url: url, description: desc }
|
114
114
|
end
|
115
115
|
|
116
|
+
def order *param_names
|
117
|
+
self.param_order = param_names
|
118
|
+
end
|
119
|
+
|
120
|
+
def param_examples exp_by = :all, examples_hash
|
121
|
+
_process_objs
|
122
|
+
exp_by = self[:parameters].map { |p| p[:name] } if exp_by == :all
|
123
|
+
# TODO: ref obj
|
124
|
+
# exp_in_params = self[:parameters].map { |p| p[:schema][:examples] }.compact
|
125
|
+
# examples_hash.map! do |key, value|
|
126
|
+
# if value == []
|
127
|
+
# if key.in?(exp_in_params.map { |e| e.keys }.flatten.uniq)
|
128
|
+
# # TODO
|
129
|
+
# end
|
130
|
+
# end
|
131
|
+
# end
|
132
|
+
self[:examples] = ExampleObj.new(examples_hash, exp_by).process
|
133
|
+
end
|
134
|
+
alias_method :examples, :param_examples
|
135
|
+
|
116
136
|
|
117
137
|
def _process_objs
|
118
138
|
self[:parameters]&.each_with_index do |p, index|
|
119
139
|
self[:parameters][index] = p.process if p.is_a?(ParamObj)
|
120
140
|
end
|
121
141
|
|
142
|
+
# Parameters sorting
|
143
|
+
self[:parameters].clone.each do |p|
|
144
|
+
self[:parameters][param_order.index(p[:name])] = p
|
145
|
+
end if param_order.present?
|
146
|
+
|
122
147
|
self[:responses]&.each do |code, obj|
|
123
148
|
self[:responses][code] = obj.process if obj.is_a?(ResponseObj)
|
124
149
|
end
|
@@ -6,7 +6,7 @@ module OpenApi
|
|
6
6
|
include DSL::CommonDSL
|
7
7
|
include DSL::Helpers
|
8
8
|
|
9
|
-
def schema component_key, type, schema_hash = { }
|
9
|
+
def schema component_key, type, schema_hash# = { }
|
10
10
|
(self[:schemas] ||= { })[component_key] = SchemaObj.new(type, schema_hash).process
|
11
11
|
end
|
12
12
|
arrow_enable :schema
|
data/lib/open_api/dsl/helpers.rb
CHANGED
@@ -11,9 +11,9 @@ module OpenApi
|
|
11
11
|
# (2) config in model: https://github.com/zhandao/zero-rails/tree/master/app/models/good.rb
|
12
12
|
# (3) jbuilder file: https://github.com/zhandao/zero-rails/blob/mster/app/views/api/v1/goods/index.json.jbuilder
|
13
13
|
# in a word, BuilderSupport let you control the `output fields and nested association infos` very easily.
|
14
|
-
if model
|
14
|
+
if model.respond_to? :show_attrs
|
15
15
|
columns = model.columns.map(&:name).map(&:to_sym)
|
16
|
-
model
|
16
|
+
model.show_attrs.map do |attr|
|
17
17
|
if columns.include? attr
|
18
18
|
index = columns.index attr
|
19
19
|
type = model.columns[index].sql_type_metadata.type.to_s.camelize
|
@@ -26,13 +26,13 @@ module OpenApi
|
|
26
26
|
end rescue next
|
27
27
|
end
|
28
28
|
else
|
29
|
-
model
|
29
|
+
model.columns.map do |column|
|
30
30
|
name = column.name.to_sym
|
31
31
|
type = column.sql_type_metadata.type.to_s.camelize
|
32
32
|
type = 'DateTime' if type == 'Datetime'
|
33
33
|
{ name => Object.const_get(type) }
|
34
34
|
end
|
35
|
-
end
|
35
|
+
end.compact.reduce({ }, :merge) rescue ''
|
36
36
|
end
|
37
37
|
|
38
38
|
# Arrow Writing:
|