zero-rails_openapi 1.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +39 -17
- data/documentation/examples/auto_gen_desc.rb +8 -5
- data/documentation/examples/auto_gen_doc.rb +86 -0
- data/documentation/examples/example_output_doc.json +2998 -0
- data/documentation/examples/examples_controller.rb +1 -1
- data/documentation/examples/goods_doc.rb +38 -0
- data/documentation/examples/open_api.rb +49 -1
- data/documentation/parameter.md +3 -2
- data/lib/oas_objs/helpers.rb +2 -0
- data/lib/oas_objs/media_type_obj.rb +1 -1
- data/lib/oas_objs/schema_obj.rb +21 -13
- data/lib/open_api.rb +0 -1
- data/lib/open_api/config.rb +95 -29
- data/lib/open_api/dsl.rb +19 -12
- data/lib/open_api/dsl_inside_block.rb +14 -5
- data/lib/open_api/generator.rb +24 -6
- data/lib/open_api/helpers.rb +35 -0
- data/lib/open_api/version.rb +1 -1
- metadata +7 -4
- data/documentation/examples/auto_gen_dsl.rb +0 -42
@@ -16,7 +16,7 @@ class Api::V1::ExamplesController < Api::V1::BaseController
|
|
16
16
|
response '567', 'query result export', :pdf, type: File
|
17
17
|
end
|
18
18
|
|
19
|
-
open_api :index, '(SUMMARY) this api blah blah ...' do
|
19
|
+
open_api :index, '(SUMMARY) this api blah blah ...', builder: :template1 do
|
20
20
|
this_api_is_invalid! 'this api is expired!'
|
21
21
|
desc 'Optional multiline or single-line Markdown-formatted description',
|
22
22
|
id: 'user id',
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class V2::GoodsDoc < BaseDoc
|
2
|
+
|
3
|
+
open_api :index, 'Get list of Goods.', builder: :index,
|
4
|
+
use: [ :Token ] do # use parameters write in AutoGenDoc#api_dry
|
5
|
+
# skip: %i[ Token ] do # you can also skip parameters
|
6
|
+
desc 'listing Goods',
|
7
|
+
view!: 'search view, allows::<br/>',
|
8
|
+
search_type!: 'search field, allows:<br/>'
|
9
|
+
|
10
|
+
query :view, String, enum: {
|
11
|
+
'all goods (default)': :all,
|
12
|
+
'only online': :online,
|
13
|
+
'only offline': :offline,
|
14
|
+
'expensive goods': :expensive,
|
15
|
+
'cheap goods': :cheap,
|
16
|
+
}
|
17
|
+
query :search_type, String, enum: %w[name creator category price]
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
open_api :create, 'Create a Good', builder: :success_or_not, use: token do
|
22
|
+
form! 'for creating a good', data: {
|
23
|
+
:name! => { type: String, desc: 'good\'s name' },
|
24
|
+
:category_id! => { type: Integer, desc: 'sub_category\'s id', npmt: true, range: { ge: 1 }, as: :cate },
|
25
|
+
:price! => { type: Float, desc: 'good\'s price', range: { ge: 0} },
|
26
|
+
# -- optional
|
27
|
+
:is_online => { type: Boolean, desc: 'it\'s online?' },
|
28
|
+
:remarks => { type: String, desc: 'remarks' },
|
29
|
+
:pic_path => { type: String, desc: 'picture url', is: :url },
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
open_api :show, 'Show a Good.', builder: :show, use: [ :Token, :id ]
|
35
|
+
|
36
|
+
|
37
|
+
open_api :destroy, 'Delete a Good.', builder: :success_or_not, use: [ :Token, :id ]
|
38
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'open_api'
|
2
2
|
|
3
|
-
OpenApi.
|
3
|
+
OpenApi::Config.tap do |c|
|
4
4
|
# [REQUIRED] The location where .json doc file will be output.
|
5
5
|
c.file_output_path = 'public/open_api'
|
6
6
|
|
@@ -82,6 +82,54 @@ OpenApi.configure do |c|
|
|
82
82
|
global_security: [{ ApiKeyAuth: [] }],
|
83
83
|
}
|
84
84
|
}
|
85
|
+
|
86
|
+
c.generate_jbuilder_file = true
|
87
|
+
c.overwrite_jbuilder_file = false
|
88
|
+
c.jbuilder_templates = {
|
89
|
+
index: (
|
90
|
+
<<-FILE
|
91
|
+
json.partial! 'api/base', total: @data.count
|
92
|
+
|
93
|
+
json.data do
|
94
|
+
# @data = @data.page(@_page).per(@_rows) if @_page || @_rows
|
95
|
+
# json.array! @data do |datum|
|
96
|
+
json.array! @data.page(@_page).per(@_rows) do |datum|
|
97
|
+
json.(datum, *datum.show_attrs) if datum.present?
|
98
|
+
end
|
99
|
+
end
|
100
|
+
FILE
|
101
|
+
),
|
102
|
+
|
103
|
+
show: (
|
104
|
+
<<-FILE
|
105
|
+
json.partial! 'api/base', total: 1
|
106
|
+
|
107
|
+
json.data do
|
108
|
+
json.array! [ @data ] do |datum|
|
109
|
+
json.(datum, *datum.show_attrs) if datum.present?
|
110
|
+
end
|
111
|
+
end
|
112
|
+
FILE
|
113
|
+
),
|
114
|
+
|
115
|
+
success: (
|
116
|
+
<<-FILE
|
117
|
+
json.partial! 'api/success'
|
118
|
+
FILE
|
119
|
+
),
|
120
|
+
|
121
|
+
success_or_not: (
|
122
|
+
<<-FILE
|
123
|
+
unless @status
|
124
|
+
# @_code, @_msg = @error_info.present? ? @error_info : ApiError.action_failed.info
|
125
|
+
end
|
126
|
+
|
127
|
+
json.partial! 'api/base', total: 0
|
128
|
+
json.data ''
|
129
|
+
FILE
|
130
|
+
),
|
131
|
+
}
|
132
|
+
|
85
133
|
end
|
86
134
|
|
87
135
|
Object.const_set('Boolean', 'boolean') # Support `Boolean` writing in DSL
|
data/documentation/parameter.md
CHANGED
@@ -15,7 +15,7 @@ int32, float, date ...
|
|
15
15
|
All the types you can use are:
|
16
16
|
- **String, 'binary', 'base64'**
|
17
17
|
- **Integer, Long, 'int32', 'int64', Float, Double**
|
18
|
-
- **File** (it will be converted as `{ type: 'string', format:
|
18
|
+
- **File** (it will be converted as `{ type: 'string', format: Config.dft_file_format }`)
|
19
19
|
- **Date, DateTime**
|
20
20
|
- **Boolean**
|
21
21
|
- **Array**: `Array[String]` or `[String]`
|
@@ -58,4 +58,5 @@ You can set the schema by following keys (all are optional), the words in parent
|
|
58
58
|
[email phone password uuid uri url time date], to overwrite it you can set it in initializer `c.is_options = %w[]`.
|
59
59
|
5. If type is Object, for describing each property's schema, the only way is use ref type, like: `{ id: :Id, name: :Name }`
|
60
60
|
- **pattern (regexp, pr, reg)**
|
61
|
-
- **default (dft, default_value)**
|
61
|
+
- **default (dft, default_value)**
|
62
|
+
- **as** # TODO
|
data/lib/oas_objs/helpers.rb
CHANGED
@@ -10,11 +10,13 @@ module OpenApi
|
|
10
10
|
proc { |_, v| truly_present? v }
|
11
11
|
end
|
12
12
|
|
13
|
+
# assign.to
|
13
14
|
def assign(value)
|
14
15
|
@assign = value.is_a?(Symbol) ? send("_#{value}") : value
|
15
16
|
self
|
16
17
|
end
|
17
18
|
|
19
|
+
# reduceee.then_merge! => for Hash
|
18
20
|
def reduceee(*values)
|
19
21
|
@assign = values.compact.reduce({ }, :merge).keep_if &value_present
|
20
22
|
self
|
@@ -7,7 +7,7 @@ module OpenApi
|
|
7
7
|
attr_accessor :media_type, :schema
|
8
8
|
def initialize(media_type, schema_hash)
|
9
9
|
self.media_type = media_type_mapping media_type
|
10
|
-
self.schema = SchemaObj.new(schema_hash
|
10
|
+
self.schema = SchemaObj.new(schema_hash.values_at(:type, :data, :t).compact.first, schema_hash)
|
11
11
|
end
|
12
12
|
|
13
13
|
def process
|
data/lib/oas_objs/schema_obj.rb
CHANGED
@@ -30,12 +30,15 @@ module OpenApi
|
|
30
30
|
processed_range,
|
31
31
|
processed_is_and_format(param_name),
|
32
32
|
{
|
33
|
-
pattern:
|
34
|
-
default:
|
33
|
+
pattern: _pattern&.inspect&.delete('/'),
|
34
|
+
default: _default,
|
35
|
+
as: _as,
|
36
|
+
permit: _permit,
|
37
|
+
not_permit: _npermit,
|
35
38
|
}
|
36
39
|
then_merge!
|
37
40
|
|
38
|
-
|
41
|
+
reduceee(processed_desc options).then_merge!
|
39
42
|
end
|
40
43
|
alias_method :process, :process_for
|
41
44
|
|
@@ -51,15 +54,17 @@ module OpenApi
|
|
51
54
|
options[:desc_inside] ? { description: result } : nil
|
52
55
|
end
|
53
56
|
|
57
|
+
# TODO: more info
|
58
|
+
# TODO: desc configure
|
54
59
|
def process_desc
|
55
60
|
if processed[:enum].present?
|
56
61
|
if @enum_info.present?
|
57
62
|
@enum_info.each_with_index do |(info, value), index|
|
58
|
-
__desc.concat "
|
63
|
+
__desc.concat "<br/>#{index + 1}/ #{info}: #{value}"
|
59
64
|
end
|
60
65
|
else
|
61
66
|
processed[:enum].each_with_index do |value, index|
|
62
|
-
__desc.concat "
|
67
|
+
__desc.concat "<br/>#{index + 1}/ #{value}"
|
63
68
|
end
|
64
69
|
end
|
65
70
|
end
|
@@ -69,7 +74,7 @@ module OpenApi
|
|
69
74
|
def processed_type(type = self.type)
|
70
75
|
t = type.class.in?([Hash, Array, Symbol]) ? type : "#{type}".downcase
|
71
76
|
if t.is_a? Hash
|
72
|
-
# For
|
77
|
+
# For supporting writing:
|
73
78
|
# form 'desc', data: {
|
74
79
|
# id!: { type: Integer, enum: 0..5, desc: 'user id' }
|
75
80
|
# }
|
@@ -87,7 +92,7 @@ module OpenApi
|
|
87
92
|
elsif t.in? %w[binary base64]
|
88
93
|
{ type: 'string', format: t}
|
89
94
|
elsif t.eql? 'file'
|
90
|
-
{ type: 'string', format:
|
95
|
+
{ type: 'string', format: Config.dft_file_format }
|
91
96
|
elsif t.eql? 'datetime'
|
92
97
|
{ type: 'string', format: 'date-time' }
|
93
98
|
else # other string
|
@@ -177,7 +182,7 @@ module OpenApi
|
|
177
182
|
end
|
178
183
|
def recognize_is_options_in(name)
|
179
184
|
# identify whether `is` patterns matched the name, if so, generate `is`.
|
180
|
-
|
185
|
+
Config.is_options.each do |pattern|
|
181
186
|
self._is = pattern or break if name.match? /#{pattern}/
|
182
187
|
end if _is.nil?
|
183
188
|
self.delete :_is if _is.in?([:x, :we])
|
@@ -188,13 +193,16 @@ module OpenApi
|
|
188
193
|
_enum: %i[ enum values allowable_values ],
|
189
194
|
_value: %i[ must_be value allowable_value ],
|
190
195
|
_range: %i[ range number_range ],
|
191
|
-
_length: %i[ length lth
|
192
|
-
_is: %i[ is_a is ], # NOT OAS Spec,
|
196
|
+
_length: %i[ length lth size ],
|
197
|
+
_is: %i[ is_a is ], # NOT OAS Spec, see documentation/parameter.md
|
193
198
|
_format: %i[ format fmt ],
|
194
|
-
_pattern: %i[ pattern regexp
|
199
|
+
_pattern: %i[ pattern regexp pt reg ],
|
195
200
|
_default: %i[ default dft default_value ],
|
196
|
-
_desc: %i[ desc description
|
197
|
-
__desc: %i[ desc! description!
|
201
|
+
_desc: %i[ desc description d ],
|
202
|
+
__desc: %i[ desc! description! d! ],
|
203
|
+
_as: %i[ as to for map mapping ], # NOT OAS Spec, it's for zero-params_processor
|
204
|
+
_permit: %i[ permit pmt ], # NOT OAS Spec, it's for zero-params_processor
|
205
|
+
_npermit: %i[ npmt not_permit unpermit ], # NOT OAS Spec, it's for zero-params_processor
|
198
206
|
}.each do |key, aliases|
|
199
207
|
define_method key do
|
200
208
|
aliases.each do |alias_name|
|
data/lib/open_api.rb
CHANGED
data/lib/open_api/config.rb
CHANGED
@@ -1,34 +1,100 @@
|
|
1
1
|
module OpenApi
|
2
2
|
module Config
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
3
|
+
# [REQUIRED] The location where .json doc file will be output.
|
4
|
+
cattr_accessor :file_output_path do
|
5
|
+
'public/open_api'
|
6
|
+
end
|
7
|
+
|
8
|
+
cattr_accessor :generate_doc do
|
9
|
+
true
|
10
|
+
end
|
11
|
+
|
12
|
+
# Everything about OAS3 is on https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.0.md
|
13
|
+
# Getting started: https://swagger.io/docs/specification/basic-structure/
|
14
|
+
cattr_accessor :register_docs do
|
15
|
+
{
|
16
|
+
# [REQUIRED] At least one doc.
|
17
|
+
zero_rails: {
|
18
|
+
# [REQUIRED] ZRO will scan all the descendants of the root_controller, and then generate their docs.
|
19
|
+
root_controller: ApplicationController,
|
20
|
+
|
21
|
+
# [REQUIRED] Info Object: The info section contains API information
|
22
|
+
info: {
|
23
|
+
# [REQUIRED] The title of the application.
|
24
|
+
title: 'Zero Rails Apis',
|
25
|
+
# [REQUIRED] The version of the OpenAPI document
|
26
|
+
# (which is distinct from the OpenAPI Specification version or the API implementation version).
|
27
|
+
version: '0.0.1'
|
28
|
+
}
|
29
|
+
}
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
cattr_accessor :is_options do
|
34
|
+
%w[ email phone password uuid uri url time date ]
|
35
|
+
end
|
36
|
+
|
37
|
+
cattr_accessor :dft_file_format do
|
38
|
+
'binary'
|
39
|
+
end
|
40
|
+
|
41
|
+
cattr_accessor :generate_jbuilder_file do
|
42
|
+
false
|
43
|
+
end
|
44
|
+
|
45
|
+
cattr_accessor :overwrite_jbuilder_file do
|
46
|
+
false
|
47
|
+
end
|
48
|
+
|
49
|
+
cattr_accessor :jbuilder_templates do
|
50
|
+
{
|
51
|
+
index: (
|
52
|
+
<<-FILE
|
53
|
+
json.partial! 'api/base', total: @data.count
|
54
|
+
|
55
|
+
json.data do
|
56
|
+
# @data = @data.page(@_page).per(@_rows) if @_page || @_rows
|
57
|
+
# json.array! @data do |datum|
|
58
|
+
json.array! @data.page(@_page).per(@_rows) do |datum|
|
59
|
+
json.(datum, *datum.show_attrs) if datum.present?
|
60
|
+
end
|
61
|
+
end
|
62
|
+
FILE
|
63
|
+
),
|
64
|
+
|
65
|
+
show: (
|
66
|
+
<<-FILE
|
67
|
+
json.partial! 'api/base', total: 1
|
68
|
+
|
69
|
+
json.data do
|
70
|
+
json.array! [ @data ] do |datum|
|
71
|
+
json.(datum, *datum.show_attrs) if datum.present?
|
72
|
+
end
|
73
|
+
end
|
74
|
+
FILE
|
75
|
+
),
|
76
|
+
|
77
|
+
success: (
|
78
|
+
<<-FILE
|
79
|
+
json.partial! 'api/success'
|
80
|
+
FILE
|
81
|
+
),
|
82
|
+
|
83
|
+
success_or_not: (
|
84
|
+
<<-FILE
|
85
|
+
unless @status
|
86
|
+
# @_code, @_msg = @error_info.present? ? @error_info : ApiError.action_failed.info
|
87
|
+
end
|
88
|
+
|
89
|
+
json.partial! 'api/base', total: 0
|
90
|
+
json.data ''
|
91
|
+
FILE
|
92
|
+
),
|
93
|
+
}
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.docs
|
97
|
+
register_docs
|
32
98
|
end
|
33
99
|
end
|
34
100
|
end
|
data/lib/open_api/dsl.rb
CHANGED
@@ -24,33 +24,40 @@ module OpenApi
|
|
24
24
|
current_ctrl.instance_eval &block if block_given?
|
25
25
|
end
|
26
26
|
|
27
|
-
def open_api method, summary = '', &block
|
27
|
+
def open_api method, summary = '', options = { }, &block
|
28
28
|
apis_set if @_ctrl_infos.nil?
|
29
29
|
|
30
30
|
# select the routing info (corresponding to the current method) from the routing list.
|
31
31
|
action_path = "#{@_ctrl_path ||= controller_path}##{method}"
|
32
32
|
routes_info = ctrl_routes_list&.select { |api| api[:action_path].match? /^#{action_path}$/ }&.first
|
33
|
-
|
33
|
+
pp "[ZRO Warnning] Routing mapping failed: #{@_ctrl_path}##{method}" and return if routes_info.nil?
|
34
34
|
|
35
|
-
# structural { path: { http_method:{ } } }, for Paths Object.
|
35
|
+
# structural { #path: { #http_method:{ } } }, for pushing into Paths Object.
|
36
36
|
path = (@_api_infos ||= { })[routes_info[:path]] ||= { }
|
37
37
|
current_api = path[routes_info[:http_verb]] =
|
38
|
-
ApiInfoObj.new(action_path)
|
38
|
+
ApiInfoObj.new(action_path, options.slice(:skip, :use))
|
39
39
|
.merge! description: '', summary: summary, operationId: method, tags: [@_apis_tag],
|
40
|
-
parameters: [ ], requestBody: '', responses: { },
|
41
|
-
security: [ ], servers: [ ]
|
40
|
+
parameters: [ ], requestBody: '', responses: { }, security: [ ], servers: [ ]
|
42
41
|
|
43
|
-
|
42
|
+
if (builder = options.values_at(:builder, :bd, :jbuilder).compact.first).present?
|
43
|
+
Generator
|
44
|
+
.generate_builder_file path: action_path.split('#').first,
|
45
|
+
action: action_path.split('#').last,
|
46
|
+
builder: builder
|
47
|
+
end
|
48
|
+
|
49
|
+
current_api.tap do |api|
|
44
50
|
[method, :all].each do |key| # blocks_store_key
|
45
|
-
@_apis_blocks&.[](key)&.each { |blk|
|
51
|
+
@_apis_blocks&.[](key)&.each { |blk| api.instance_eval &blk }
|
46
52
|
end
|
47
|
-
|
48
|
-
|
49
|
-
|
53
|
+
api.param_use = nil
|
54
|
+
api.instance_eval &block if block_given?
|
55
|
+
api.instance_eval { process_params }
|
56
|
+
api.delete_if { |_, v| v.blank? }
|
50
57
|
end
|
51
58
|
end
|
52
59
|
|
53
|
-
#
|
60
|
+
# method could be symbol array, like: %i[ .. ]
|
54
61
|
def api_dry method = :all, desc = '', &block
|
55
62
|
@_apis_blocks ||= { }
|
56
63
|
if method.is_a? Array
|
@@ -3,6 +3,7 @@ require 'oas_objs/param_obj'
|
|
3
3
|
require 'oas_objs/response_obj'
|
4
4
|
require 'oas_objs/request_body_obj'
|
5
5
|
require 'oas_objs/ref_obj'
|
6
|
+
require 'open_api/helpers'
|
6
7
|
|
7
8
|
module OpenApi
|
8
9
|
module DSL
|
@@ -100,10 +101,13 @@ module OpenApi
|
|
100
101
|
|
101
102
|
class ApiInfoObj < Hash
|
102
103
|
include DSL::CommonDSL
|
104
|
+
include DSL::Helpers
|
103
105
|
|
104
|
-
attr_accessor :action_path
|
105
|
-
def initialize(action_path)
|
106
|
+
attr_accessor :action_path, :param_skip, :param_use
|
107
|
+
def initialize(action_path, options = { })
|
106
108
|
self.action_path = action_path
|
109
|
+
self.param_skip = options[:skip] || [ ]
|
110
|
+
self.param_use = options[:use] || [ ]
|
107
111
|
end
|
108
112
|
|
109
113
|
def this_api_is_invalid! explain = ''
|
@@ -131,6 +135,9 @@ module OpenApi
|
|
131
135
|
end
|
132
136
|
|
133
137
|
def param param_type, name, type, required, schema_hash = { }
|
138
|
+
return if param_skip.include? name
|
139
|
+
return unless param_use.include? name if param_use.present?
|
140
|
+
|
134
141
|
if @inputs_descs&.[](name).present?
|
135
142
|
schema_hash[:desc] = @inputs_descs[name]
|
136
143
|
elsif @inputs_descs&.[]("#{name}!".to_sym).present?
|
@@ -139,7 +146,9 @@ module OpenApi
|
|
139
146
|
|
140
147
|
param_obj = ParamObj.new(name, param_type, type, required, schema_hash)
|
141
148
|
# The definition of the same name parameter will be overwritten
|
142
|
-
index = self[:parameters].map
|
149
|
+
index = self[:parameters].map do |p|
|
150
|
+
p.processed[:name] if p.is_a? ParamObj
|
151
|
+
end.index name
|
143
152
|
if index.present?
|
144
153
|
self[:parameters][index] = param_obj
|
145
154
|
else
|
@@ -148,8 +157,8 @@ module OpenApi
|
|
148
157
|
end
|
149
158
|
|
150
159
|
def process_params
|
151
|
-
self[:parameters].each_with_index do |
|
152
|
-
self[:parameters][index] =
|
160
|
+
self[:parameters].each_with_index do |p, index|
|
161
|
+
self[:parameters][index] = p.is_a?(ParamObj) ? p.process : p
|
153
162
|
end
|
154
163
|
end
|
155
164
|
|