zero-rails_openapi 1.3.3 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +91 -0
- data/Gemfile.lock +7 -5
- data/LICENSE.txt +1 -1
- data/README.md +182 -55
- data/documentation/examples/auto_gen_desc.rb +9 -11
- data/documentation/examples/auto_gen_doc.rb +3 -3
- data/documentation/examples/examples_controller.rb +27 -29
- data/documentation/examples/goods_doc.rb +9 -8
- data/documentation/examples/open_api.rb +28 -17
- data/documentation/examples/output_example.json +1417 -798
- data/lib/oas_objs/combined_schema.rb +28 -0
- data/lib/oas_objs/example_obj.rb +1 -1
- data/lib/oas_objs/helpers.rb +3 -2
- data/lib/oas_objs/media_type_obj.rb +1 -1
- data/lib/oas_objs/param_obj.rb +6 -5
- data/lib/oas_objs/response_obj.rb +1 -1
- data/lib/oas_objs/schema_obj.rb +11 -8
- data/lib/open_api.rb +8 -0
- data/lib/open_api/config.rb +9 -52
- data/lib/open_api/config_dsl.rb +27 -9
- data/lib/open_api/dsl.rb +17 -22
- data/lib/open_api/dsl/api_info_obj.rb +24 -21
- data/lib/open_api/dsl/common_dsl.rb +1 -0
- data/lib/open_api/dsl/ctrl_info_obj.rb +35 -7
- data/lib/open_api/dsl/helpers.rb +8 -9
- data/lib/open_api/generator.rb +35 -27
- data/lib/open_api/version.rb +1 -1
- metadata +5 -3
@@ -6,8 +6,13 @@ module OpenApi
|
|
6
6
|
include DSL::CommonDSL
|
7
7
|
include DSL::Helpers
|
8
8
|
|
9
|
-
def schema component_key, type,
|
10
|
-
(
|
9
|
+
def schema component_key, type = nil, one_of: nil, all_of: nil, any_of: nil, not: nil, **schema_hash
|
10
|
+
(schema_hash = type) and (type = type.delete(:type)) if type.is_a?(Hash) && type.key?(:type)
|
11
|
+
type = schema_hash[:type] if type.nil?
|
12
|
+
|
13
|
+
combined_schema = one_of || all_of || any_of || (_not = binding.local_variable_get(:not))
|
14
|
+
combined_schema = CombinedSchema.new(one_of: one_of, all_of: all_of, any_of: any_of, _not: _not) if combined_schema
|
15
|
+
(self[:schemas] ||= { })[component_key] = combined_schema&.process || SchemaObj.new(type, schema_hash).process
|
11
16
|
end
|
12
17
|
arrow_enable :schema
|
13
18
|
|
@@ -16,19 +21,22 @@ module OpenApi
|
|
16
21
|
end
|
17
22
|
|
18
23
|
def param component_key, param_type, name, type, required, schema_hash = { }
|
19
|
-
(self[:parameters] ||= { })[component_key] =
|
20
|
-
ParamObj.new(name, param_type, type, required, schema_hash).process
|
24
|
+
(self[:parameters] ||= { })[component_key] = ParamObj.new(name, param_type, type, required, schema_hash).process
|
21
25
|
end
|
22
26
|
|
23
|
-
def _param_agent component_key, name, type,
|
27
|
+
def _param_agent component_key, name, type = nil, one_of: nil, all_of: nil, any_of: nil, not: nil, **schema_hash
|
28
|
+
(schema_hash = type) and (type = type.delete(:type)) if type.is_a?(Hash) && type.key?(:type)
|
29
|
+
type = schema_hash[:type] if type.nil?
|
30
|
+
|
31
|
+
combined_schema = one_of || all_of || any_of || (_not = binding.local_variable_get(:not))
|
32
|
+
schema_hash = CombinedSchema.new(one_of: one_of, all_of: all_of, any_of: any_of, _not: _not) if combined_schema
|
24
33
|
param component_key,
|
25
34
|
"#{@param_type}".delete('!'), name, type, (@param_type['!'] ? :req : :opt), schema_hash
|
26
35
|
end
|
27
36
|
arrow_enable :_param_agent
|
28
37
|
|
29
38
|
def request_body component_key, required, media_type, desc = '', schema_hash = { }
|
30
|
-
(self[:requestBodies] ||= { })[component_key] =
|
31
|
-
RequestBodyObj.new(required, media_type, desc, schema_hash).process
|
39
|
+
(self[:requestBodies] ||= { })[component_key] = RequestBodyObj.new(required, media_type, desc, schema_hash).process
|
32
40
|
end
|
33
41
|
|
34
42
|
def _request_body_agent component_key, media_type, desc = '', schema_hash = { }
|
@@ -40,6 +48,26 @@ module OpenApi
|
|
40
48
|
arrow_enable :resp # alias_method 竟然也会指向旧的方法?
|
41
49
|
arrow_enable :response
|
42
50
|
|
51
|
+
def security_scheme scheme_name, other_info# = { }
|
52
|
+
other_info[:description] = other_info.delete(:desc) if other_info[:desc]
|
53
|
+
(self[:securitySchemes] ||= { })[scheme_name] = other_info
|
54
|
+
end
|
55
|
+
arrow_enable :security_scheme
|
56
|
+
|
57
|
+
alias auth_scheme security_scheme
|
58
|
+
|
59
|
+
def base_auth scheme_name, other_info = { }
|
60
|
+
security_scheme scheme_name, { type: 'http', scheme: 'basic' }.merge(other_info)
|
61
|
+
end
|
62
|
+
|
63
|
+
def bearer_auth scheme_name, format = 'JWT', other_info = { }
|
64
|
+
security_scheme scheme_name, { type: 'http', scheme: 'bearer', bearerFormat: format }.merge(other_info)
|
65
|
+
end
|
66
|
+
|
67
|
+
def api_key scheme_name, field:, in:, **other_info
|
68
|
+
_in = binding.local_variable_get(:in)
|
69
|
+
security_scheme scheme_name, { type: 'apiKey', name: field, in: _in }.merge(other_info)
|
70
|
+
end
|
43
71
|
|
44
72
|
def _process_objs
|
45
73
|
self[:responses]&.each do |code, obj|
|
data/lib/open_api/dsl/helpers.rb
CHANGED
@@ -10,16 +10,16 @@ module OpenApi
|
|
10
10
|
# (1) BuilderSupport module: https://github.com/zhandao/zero-rails/blob/master/app/models/concerns/builder_support.rb
|
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
14
|
if model.respond_to? :show_attrs
|
15
|
-
columns = model.
|
15
|
+
columns = model.column_names.map(&:to_sym)
|
16
16
|
model.show_attrs.map do |attr|
|
17
|
-
if columns.include?
|
18
|
-
index = columns.index
|
17
|
+
if columns.include?(attr)
|
18
|
+
index = columns.index(attr)
|
19
19
|
type = model.columns[index].sql_type_metadata.type.to_s.camelize
|
20
20
|
type = 'DateTime' if type == 'Datetime'
|
21
21
|
{ attr => Object.const_get(type) }
|
22
|
-
elsif attr.match?
|
22
|
+
elsif attr.match?(/_info/)
|
23
23
|
# TODO: 如何获知关系是 many?因为不能只判断结尾是否 ‘s’
|
24
24
|
assoc_model = Object.const_get(attr.to_s.split('_').first.singularize.camelize)
|
25
25
|
{ attr => load_schema(assoc_model) }
|
@@ -27,10 +27,9 @@ module OpenApi
|
|
27
27
|
end
|
28
28
|
else
|
29
29
|
model.columns.map do |column|
|
30
|
-
name = column.name.to_sym
|
31
30
|
type = column.sql_type_metadata.type.to_s.camelize
|
32
31
|
type = 'DateTime' if type == 'Datetime'
|
33
|
-
{ name => Object.const_get(type) }
|
32
|
+
{ column.name.to_sym => Object.const_get(type) }
|
34
33
|
end
|
35
34
|
end.compact.reduce({ }, :merge) rescue ''
|
36
35
|
end
|
@@ -43,8 +42,8 @@ module OpenApi
|
|
43
42
|
# the key-value (arrow) writing is easy to understand.
|
44
43
|
def arrow_writing_support
|
45
44
|
proc do |args, executor|
|
46
|
-
_args = args.size == 1 && args.first.is_a?(Hash) ? args[0].to_a.flatten : args
|
47
|
-
send
|
45
|
+
_args = (args.size == 1 && args.first.is_a?(Hash)) ? args[0].to_a.flatten : args
|
46
|
+
send(executor, *_args)
|
48
47
|
end
|
49
48
|
end
|
50
49
|
|
data/lib/open_api/generator.rb
CHANGED
@@ -7,25 +7,28 @@ module OpenApi
|
|
7
7
|
end
|
8
8
|
|
9
9
|
module ClassMethods
|
10
|
-
def generate_docs(
|
11
|
-
Dir['./app/controllers
|
10
|
+
def generate_docs(doc_name = nil)
|
11
|
+
Dir['./app/controllers/**/*_controller.rb'].each do |file|
|
12
|
+
# Do Not `require`!
|
13
|
+
# It causes problems, such as making `skip_before_action` not working.
|
14
|
+
file.sub('./app/controllers/', '').sub('.rb', '').camelize.constantize
|
15
|
+
end
|
12
16
|
# TODO: _doc should be configured
|
13
17
|
Dir['./app/**/*_doc.rb'].each { |file| require file }
|
14
|
-
if
|
15
|
-
[{
|
18
|
+
if doc_name.present?
|
19
|
+
[{ doc_name => generate_doc(doc_name) }]
|
16
20
|
else
|
17
21
|
Config.docs.keys.map { |api_key| { api_key => generate_doc(api_key) } }.reduce({ }, :merge)
|
18
22
|
end
|
19
23
|
end
|
20
24
|
|
21
|
-
def generate_doc(
|
22
|
-
settings = Config.docs[
|
25
|
+
def generate_doc(doc_name)
|
26
|
+
settings = Config.docs[doc_name]
|
23
27
|
doc = { openapi: '3.0.0' }.merge(settings.slice :info, :servers).merge(
|
24
|
-
# TODO: rename to just `security`
|
25
28
|
security: settings[:global_security], tags: [ ], paths: { },
|
26
29
|
components: {
|
27
|
-
securitySchemes: settings[:
|
28
|
-
schemas: { }
|
30
|
+
securitySchemes: settings[:security_schemes] || { },
|
31
|
+
schemas: { }, parameters: { }, requestBodies: { }
|
29
32
|
}
|
30
33
|
)
|
31
34
|
|
@@ -35,14 +38,13 @@ module OpenApi
|
|
35
38
|
doc[:paths].merge! ctrl.instance_variable_get('@_api_infos') || { }
|
36
39
|
doc[:tags] << ctrl_infos[:tag]
|
37
40
|
doc[:components].merge! ctrl_infos[:components] || { }
|
38
|
-
|
41
|
+
OpenApi.paths_index[ctrl.instance_variable_get('@_ctrl_path')] = doc_name
|
39
42
|
end
|
40
43
|
doc[:components].delete_if { |_, v| v.blank? }
|
41
44
|
doc[:tags] = doc[:tags].sort { |a, b| a[:name] <=> b[:name] }
|
42
45
|
doc[:paths] = doc[:paths].sort.to_h
|
43
46
|
|
44
|
-
|
45
|
-
ActiveSupport::HashWithIndifferentAccess.new(doc.delete_if { |_, v| v.blank? })
|
47
|
+
OpenApi.docs[doc_name] ||= ActiveSupport::HashWithIndifferentAccess.new(doc.delete_if { |_, v| v.blank? })
|
46
48
|
end
|
47
49
|
|
48
50
|
def write_docs(generate_files: true)
|
@@ -51,12 +53,11 @@ module OpenApi
|
|
51
53
|
output_path = Config.file_output_path
|
52
54
|
FileUtils.mkdir_p output_path
|
53
55
|
max_length = docs.keys.map(&:size).sort.last
|
54
|
-
puts '[ZRO] * * * * * *'
|
56
|
+
# puts '[ZRO] * * * * * *'
|
55
57
|
docs.each do |doc_name, doc|
|
56
|
-
puts "[ZRO]
|
58
|
+
puts "[ZRO] `#{doc_name.to_s.rjust(max_length)}.json` has been generated."
|
57
59
|
File.open("#{output_path}/#{doc_name}.json", 'w') { |file| file.write JSON.pretty_generate doc }
|
58
60
|
end
|
59
|
-
# pp $open_apis
|
60
61
|
end
|
61
62
|
end
|
62
63
|
|
@@ -69,30 +70,37 @@ module OpenApi
|
|
69
70
|
FileUtils.mkdir_p dir_path
|
70
71
|
file_path = "#{dir_path}/#{action}.json.jbuilder"
|
71
72
|
|
72
|
-
if Config.overwrite_jbuilder_file || !File.
|
73
|
+
if Config.overwrite_jbuilder_file || !File.exist?(file_path)
|
73
74
|
File.open(file_path, 'w') { |file| file.write Config.jbuilder_templates[builder] }
|
74
75
|
puts "[ZRO] JBuilder file has been generated: #{path}/#{action}"
|
75
76
|
end
|
76
77
|
end
|
77
78
|
|
78
79
|
def self.routes_list
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
80
|
+
routes =
|
81
|
+
if (f = Config.rails_routes_file)
|
82
|
+
File.read(f)
|
83
|
+
else
|
84
|
+
# ref https://github.com/rails/rails/blob/master/railties/lib/rails/tasks/routes.rake
|
85
|
+
require './config/routes'
|
86
|
+
all_routes = Rails.application.routes.routes
|
87
|
+
require 'action_dispatch/routing/inspector'
|
88
|
+
inspector = ActionDispatch::Routing::RoutesInspector.new(all_routes)
|
89
|
+
inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, nil)
|
90
|
+
end
|
91
|
+
|
92
|
+
@routes_list ||= routes.split("\n").drop(1).map do |line|
|
93
|
+
next unless line.match?('#')
|
94
|
+
infos = line.match(/[A-Z|].*/).to_s.split(' ') # => [GET, /api/v1/examples/:id, api/v1/examples#index]
|
84
95
|
|
85
|
-
@routes_list ||=
|
86
|
-
inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, nil).split("\n").drop(1).map do |line|
|
87
|
-
infos = line.match(/[A-Z].*/).to_s.split(' ') # => [GET, /api/v1/examples/:id, api/v1/examples#index]
|
88
96
|
{
|
89
|
-
http_verb: infos[0].downcase, # => "get"
|
97
|
+
http_verb: infos[0].downcase, # => "get" / "get|post"
|
90
98
|
path: infos[1][0..-11].split('/').map do |item|
|
91
99
|
item[':'] ? "{#{item[1..-1]}}" : item
|
92
100
|
end.join('/'), # => "/api/v1/examples/{id}"
|
93
101
|
action_path: infos[2] # => "api/v1/examples#index"
|
94
102
|
} rescue next
|
95
|
-
end.compact.group_by {|api| api[:action_path].split('#').first } # => { "api/v1/examples" => [..] }, group by paths
|
103
|
+
end.compact.group_by { |api| api[:action_path].split('#').first } # => { "api/v1/examples" => [..] }, group by paths
|
96
104
|
end
|
97
105
|
|
98
106
|
def self.get_actions_by_ctrl_path(ctrl_path)
|
@@ -104,7 +112,7 @@ module OpenApi
|
|
104
112
|
def self.find_path_httpverb_by(ctrl_path, action)
|
105
113
|
routes_list[ctrl_path]&.map do |action_info|
|
106
114
|
if action_info[:action_path].split('#').last == action.to_s
|
107
|
-
return [ action_info[:path], action_info[:http_verb] ]
|
115
|
+
return [ action_info[:path], action_info[:http_verb].split('|').first ]
|
108
116
|
end
|
109
117
|
end
|
110
118
|
nil
|
data/lib/open_api/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zero-rails_openapi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- zhandao
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-12-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -92,6 +92,7 @@ extra_rdoc_files: []
|
|
92
92
|
files:
|
93
93
|
- ".gitignore"
|
94
94
|
- ".rspec"
|
95
|
+
- ".rubocop.yml"
|
95
96
|
- ".travis.yml"
|
96
97
|
- CODE_OF_CONDUCT.md
|
97
98
|
- Gemfile
|
@@ -109,6 +110,7 @@ files:
|
|
109
110
|
- documentation/examples/open_api.rb
|
110
111
|
- documentation/examples/output_example.json
|
111
112
|
- documentation/parameter.md
|
113
|
+
- lib/oas_objs/combined_schema.rb
|
112
114
|
- lib/oas_objs/example_obj.rb
|
113
115
|
- lib/oas_objs/helpers.rb
|
114
116
|
- lib/oas_objs/media_type_obj.rb
|
@@ -148,7 +150,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
148
150
|
version: '0'
|
149
151
|
requirements: []
|
150
152
|
rubyforge_project:
|
151
|
-
rubygems_version: 2.6.
|
153
|
+
rubygems_version: 2.6.13
|
152
154
|
signing_key:
|
153
155
|
specification_version: 4
|
154
156
|
summary: Generate the OpenAPI Specification 3 documentation for Rails application.
|