zero-rails_openapi 1.3.3 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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.
|