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
@@ -0,0 +1,28 @@
|
|
1
|
+
module OpenApi
|
2
|
+
module DSL
|
3
|
+
# https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/
|
4
|
+
# https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#schemaObject
|
5
|
+
class CombinedSchema < Hash
|
6
|
+
attr_accessor :processed
|
7
|
+
|
8
|
+
def initialize(combined_schema)
|
9
|
+
self.processed = { }
|
10
|
+
|
11
|
+
combined_schema.delete_if { |_, v| v.nil? }
|
12
|
+
@mode = combined_schema.keys.first.to_s.sub('_not', 'not').camelize(:lower).to_sym
|
13
|
+
@schemas = combined_schema.values.first
|
14
|
+
end
|
15
|
+
|
16
|
+
def process_for(param_name = nil, options = { desc_inside: false })
|
17
|
+
processed.tap do |it|
|
18
|
+
it[@mode] = @schemas.map do |schema|
|
19
|
+
type = schema.is_a?(Hash) ? schema[:type] : schema
|
20
|
+
schema = { } unless schema.is_a?(Hash)
|
21
|
+
SchemaObj.new(type, schema).process_for(param_name, options) end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
alias process process_for
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/oas_objs/example_obj.rb
CHANGED
data/lib/oas_objs/helpers.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
module OpenApi
|
2
2
|
module Helpers
|
3
|
-
|
4
3
|
# TODO: comment-block doc
|
5
4
|
def truly_present?(obj)
|
6
5
|
obj == false || obj.present?
|
@@ -23,11 +22,13 @@ module OpenApi
|
|
23
22
|
end
|
24
23
|
|
25
24
|
def to_processed(who)
|
25
|
+
return processed unless truly_present?(@assign)
|
26
|
+
|
26
27
|
if who.is_a?(Symbol)
|
27
28
|
send("#{who}=", @assign)
|
28
29
|
else
|
29
30
|
processed[who.to_sym] = @assign
|
30
|
-
end
|
31
|
+
end
|
31
32
|
|
32
33
|
processed
|
33
34
|
end
|
@@ -21,7 +21,7 @@ module OpenApi
|
|
21
21
|
def process
|
22
22
|
schema_processed = schema.process
|
23
23
|
result = schema_processed.values.join.blank? ? { } : { schema: schema_processed }
|
24
|
-
result
|
24
|
+
result[:examples] = examples.process unless examples.nil?
|
25
25
|
media_type.nil? ? { } : { media_type => result }
|
26
26
|
end
|
27
27
|
|
data/lib/oas_objs/param_obj.rb
CHANGED
@@ -7,19 +7,20 @@ module OpenApi
|
|
7
7
|
include Helpers
|
8
8
|
|
9
9
|
attr_accessor :processed, :schema
|
10
|
-
|
10
|
+
|
11
|
+
def initialize(name, param_type, type, required, schema)
|
11
12
|
self.processed = {
|
12
13
|
name: name,
|
13
14
|
in: param_type,
|
14
|
-
required: required.to_s.match?(/req/)
|
15
|
+
required: required.to_s.match?(/req/)
|
15
16
|
}
|
16
|
-
self.schema = SchemaObj.new(type,
|
17
|
-
merge!
|
17
|
+
self.schema = schema.is_a?(CombinedSchema) ? schema : SchemaObj.new(type, schema)
|
18
|
+
merge! schema
|
18
19
|
end
|
19
20
|
|
20
21
|
def process
|
21
22
|
assign(desc).to_processed 'description'
|
22
|
-
processed.tap { |it| it[:schema] = schema.process_for
|
23
|
+
processed.tap { |it| it[:schema] = schema.process_for(processed[:name]) }
|
23
24
|
end
|
24
25
|
|
25
26
|
def desc
|
data/lib/oas_objs/schema_obj.rb
CHANGED
@@ -34,7 +34,7 @@ module OpenApi
|
|
34
34
|
{
|
35
35
|
pattern: _pattern&.inspect&.delete('/'),
|
36
36
|
default: _default.nil? ? nil : '_default',
|
37
|
-
examples: self[:examples].present? ? ExampleObj.new(self[:examples], self[:exp_by]).process : nil
|
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 }
|
40
40
|
then_merge!
|
@@ -75,14 +75,17 @@ module OpenApi
|
|
75
75
|
end
|
76
76
|
|
77
77
|
def processed_type(type = self.type)
|
78
|
-
t = type.class.in?([Hash, Array, Symbol]) ? type :
|
78
|
+
t = type.class.in?([Hash, Array, Symbol]) ? type : type.to_s.downcase
|
79
79
|
if t.is_a? Hash
|
80
|
-
# For supporting
|
80
|
+
# For supporting this:
|
81
81
|
# form 'desc', data: {
|
82
82
|
# id!: { type: Integer, enum: 0..5, desc: 'user id' }
|
83
83
|
# }
|
84
|
-
if t.key?
|
85
|
-
SchemaObj.new(t[:type], t).process_for
|
84
|
+
if t.key?(:type)
|
85
|
+
SchemaObj.new(t[:type], t).process_for(@prop_name, desc_inside: true)
|
86
|
+
# For supporting combined schema in nested schema.
|
87
|
+
elsif (t.keys & %i[ one_of any_of all_of not ]).present?
|
88
|
+
CombinedSchema.new(t).process_for(@prop_name, desc_inside: true)
|
86
89
|
else
|
87
90
|
recursive_obj_type t
|
88
91
|
end
|
@@ -104,7 +107,7 @@ module OpenApi
|
|
104
107
|
end
|
105
108
|
|
106
109
|
def recursive_obj_type(t) # DSL use { prop_name: prop_type } to represent object structure
|
107
|
-
return processed_type(t) if !t.is_a?(Hash) || t.
|
110
|
+
return processed_type(t) if !t.is_a?(Hash) || (t.keys & %i[ type one_of any_of all_of not ]).present?
|
108
111
|
|
109
112
|
_schema = {
|
110
113
|
type: 'object',
|
@@ -116,7 +119,7 @@ module OpenApi
|
|
116
119
|
_schema[:required] << "#{prop_name}".delete('!') if prop_name['!']
|
117
120
|
_schema[:properties]["#{prop_name}".delete('!').to_sym] = recursive_obj_type prop_type
|
118
121
|
end
|
119
|
-
_schema.keep_if
|
122
|
+
_schema.keep_if(&value_present)
|
120
123
|
end
|
121
124
|
|
122
125
|
def recursive_array_type(t)
|
@@ -185,7 +188,7 @@ module OpenApi
|
|
185
188
|
def recognize_is_options_in(name)
|
186
189
|
# identify whether `is` patterns matched the name, if so, generate `is`.
|
187
190
|
Config.is_options.each do |pattern|
|
188
|
-
self._is = pattern or break if name.match?
|
191
|
+
self._is = pattern or break if name.match?(/#{pattern}/)
|
189
192
|
end if _is.nil?
|
190
193
|
end
|
191
194
|
|
data/lib/open_api.rb
CHANGED
data/lib/open_api/config.rb
CHANGED
@@ -14,12 +14,16 @@ module OpenApi
|
|
14
14
|
true
|
15
15
|
end
|
16
16
|
|
17
|
+
cattr_accessor :rails_routes_file do
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
|
17
21
|
# Everything about OAS3 is on https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.0.md
|
18
22
|
# Getting started: https://swagger.io/docs/specification/basic-structure/
|
19
|
-
cattr_accessor :
|
23
|
+
cattr_accessor :open_api_docs do
|
20
24
|
{
|
21
25
|
# # [REQUIRED] At least one doc.
|
22
|
-
#
|
26
|
+
# zero_rails: {
|
23
27
|
# # [REQUIRED] ZRO will scan all the descendants of the root_controller, and then generate their docs.
|
24
28
|
# root_controller: ApplicationController,
|
25
29
|
#
|
@@ -36,7 +40,7 @@ module OpenApi
|
|
36
40
|
end
|
37
41
|
|
38
42
|
cattr_accessor :is_options do
|
39
|
-
%w[ email phone password uuid uri url time date ]
|
43
|
+
%w[ email phone mobile password uuid uri url time date ]
|
40
44
|
end
|
41
45
|
|
42
46
|
cattr_accessor :dft_file_format do
|
@@ -52,58 +56,11 @@ module OpenApi
|
|
52
56
|
end
|
53
57
|
|
54
58
|
cattr_accessor :jbuilder_templates do
|
55
|
-
{
|
56
|
-
index: (
|
57
|
-
<<~FILE
|
58
|
-
# *** Generated by ZRO [ please make sure that you have checked this file ] ***
|
59
|
-
json.partial! 'api/base', total: @data.size
|
60
|
-
|
61
|
-
json.data do
|
62
|
-
# @data = @data.page(@_page).per(@_rows) if @_page || @_rows
|
63
|
-
# json.array! @data do |datum|
|
64
|
-
json.array! @data.page(@_page).per(@_rows) do |datum|
|
65
|
-
json.(datum, *datum.show_attrs) if datum.present?
|
66
|
-
end
|
67
|
-
end
|
68
|
-
FILE
|
69
|
-
),
|
70
|
-
|
71
|
-
show: (
|
72
|
-
<<~FILE
|
73
|
-
# *** Generated by ZRO [ please make sure that you have checked this file ] ***
|
74
|
-
json.partial! 'api/base', total: 1
|
75
|
-
|
76
|
-
json.data do
|
77
|
-
json.array! [ @data ] do |datum|
|
78
|
-
json.(datum, *datum.show_attrs) if datum.present?
|
79
|
-
end
|
80
|
-
end
|
81
|
-
FILE
|
82
|
-
),
|
83
|
-
|
84
|
-
success: (
|
85
|
-
<<~FILE
|
86
|
-
# *** Generated by ZRO [ please make sure that you have checked this file ] ***
|
87
|
-
json.partial! 'api/success'
|
88
|
-
FILE
|
89
|
-
),
|
90
|
-
|
91
|
-
success_or_not: (
|
92
|
-
<<~FILE
|
93
|
-
# *** Generated by ZRO [ please make sure that you have checked this file ] ***
|
94
|
-
unless @status
|
95
|
-
# @_code, @_msg = @error_info.present? ? @error_info : ApiError.action_failed.info
|
96
|
-
end
|
97
|
-
|
98
|
-
json.partial! 'api/base', total: 0
|
99
|
-
json.data ''
|
100
|
-
FILE
|
101
|
-
),
|
102
|
-
}
|
59
|
+
{ }
|
103
60
|
end
|
104
61
|
|
105
62
|
def self.docs
|
106
|
-
|
63
|
+
open_api_docs
|
107
64
|
end
|
108
65
|
end
|
109
66
|
end
|
data/lib/open_api/config_dsl.rb
CHANGED
@@ -4,27 +4,45 @@ module OpenApi
|
|
4
4
|
base.class_eval do
|
5
5
|
module_function
|
6
6
|
|
7
|
-
def
|
7
|
+
def open_api name, root_controller:
|
8
8
|
@api = name
|
9
|
-
|
9
|
+
open_api_docs[name] = { root_controller: root_controller }
|
10
10
|
end
|
11
11
|
|
12
12
|
def info version:, title:, **addition
|
13
|
-
|
13
|
+
open_api_docs[@api][:info] = { version: version, title: title, **addition }
|
14
14
|
end
|
15
15
|
|
16
16
|
def server url, desc: ''
|
17
|
-
(
|
17
|
+
(open_api_docs[@api][:servers] ||= [ ]) << { url: url, description: desc }
|
18
18
|
end
|
19
19
|
|
20
|
-
def
|
21
|
-
|
20
|
+
def security_scheme scheme_name, other_info# = { }
|
21
|
+
other_info[:description] = other_info.delete(:desc) if other_info.key?(:desc)
|
22
|
+
(open_api_docs[@api][:security_schemes] ||= { })[scheme_name] = other_info
|
22
23
|
end
|
23
24
|
|
24
|
-
|
25
|
+
def base_auth scheme_name, other_info = { }
|
26
|
+
security_scheme scheme_name, { type: 'http', scheme: 'basic' }.merge(other_info)
|
27
|
+
end
|
28
|
+
|
29
|
+
def bearer_auth scheme_name, format = 'JWT', other_info = { }
|
30
|
+
security_scheme scheme_name, { type: 'http', scheme: 'bearer', bearerFormat: format }.merge(other_info)
|
31
|
+
end
|
32
|
+
|
33
|
+
def api_key scheme_name, field:, in:, **other_info
|
34
|
+
_in = binding.local_variable_get(:in)
|
35
|
+
security_scheme scheme_name, { type: 'apiKey', name: field, in: _in }.merge(other_info)
|
36
|
+
end
|
37
|
+
|
38
|
+
def global_security_require scheme_name, scopes: [ ]
|
39
|
+
(open_api_docs[@api][:global_security] ||= [ ]) << { scheme_name => scopes }
|
40
|
+
end
|
25
41
|
|
26
|
-
|
27
|
-
|
42
|
+
class << self
|
43
|
+
alias global_security global_security_require
|
44
|
+
alias global_auth global_security_require
|
45
|
+
alias auth_scheme security_scheme
|
28
46
|
end
|
29
47
|
end
|
30
48
|
end
|
data/lib/open_api/dsl.rb
CHANGED
@@ -30,40 +30,35 @@ module OpenApi
|
|
30
30
|
current_ctrl._process_objs
|
31
31
|
end
|
32
32
|
|
33
|
-
def
|
33
|
+
def api action, summary = '', http: nil, builder: nil, skip: [ ], use: [ ], &block
|
34
34
|
apis_tag if @_ctrl_infos.nil?
|
35
|
-
|
36
35
|
# select the routing info (corresponding to the current method) from routing list.
|
37
36
|
action_path = "#{@_ctrl_path ||= controller_path}##{action}"
|
38
|
-
routes_info = ctrl_routes_list&.select { |api| api[:action_path].match?
|
37
|
+
routes_info = ctrl_routes_list&.select { |api| api[:action_path].match?(/^#{action_path}$/) }&.first
|
39
38
|
pp "[ZRO Warning] Routing mapping failed: #{@_ctrl_path}##{action}" and return if routes_info.nil?
|
40
|
-
Generator.generate_builder_file(action_path, builder)
|
39
|
+
Generator.generate_builder_file(action_path, builder)
|
41
40
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
41
|
+
api = ApiInfoObj.new(action_path, skip: Array(skip), use: Array(use))
|
42
|
+
.merge! description: '', summary: summary, operationId: action, tags: [@_apis_tag],
|
43
|
+
parameters: [ ], requestBody: '', responses: { }, security: [ ], servers: [ ]
|
44
|
+
[action, :all].each { |blk_key| @_api_dry_blocks&.[](blk_key)&.each { |blk| api.instance_eval(&blk) } }
|
45
|
+
api.param_use = [ ] # `skip` and `use` only affect `api_dry`'s blocks
|
46
|
+
api.instance_eval(&block) if block_given?
|
47
|
+
api._process_objs
|
48
|
+
api.delete_if { |_, v| v.blank? }
|
48
49
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
end
|
53
|
-
api.param_use = [ ] # skip 和 use 是对 dry 块而言的
|
54
|
-
api.instance_eval(&block) if block_given?
|
55
|
-
api._process_objs
|
56
|
-
api.delete_if { |_, v| v.blank? }
|
57
|
-
end
|
50
|
+
path = (@_api_infos ||= { })[routes_info[:path]] ||= { }
|
51
|
+
http_verbs = (http || routes_info[:http_verb]).split('|')
|
52
|
+
http_verbs.each { |verb| path[verb] = api }
|
58
53
|
end
|
59
54
|
|
60
55
|
# method could be symbol array, like: %i[ .. ]
|
61
56
|
def api_dry action = :all, desc = '', &block
|
62
|
-
@
|
57
|
+
@_api_dry_blocks ||= { }
|
63
58
|
if action.is_a? Array
|
64
|
-
action.each { |m| (@
|
59
|
+
action.each { |m| (@_api_dry_blocks[m.to_sym] ||= [ ]) << block }
|
65
60
|
else
|
66
|
-
(@
|
61
|
+
(@_api_dry_blocks[action.to_sym] ||= [ ]) << block
|
67
62
|
end
|
68
63
|
end
|
69
64
|
|
@@ -30,7 +30,7 @@ module OpenApi
|
|
30
30
|
|
31
31
|
def param param_type, name, type, required, schema_hash = { }
|
32
32
|
return if param_skip.include?(name)
|
33
|
-
return if param_use.present? &&
|
33
|
+
return if param_use.present? && param_use.exclude?(name)
|
34
34
|
|
35
35
|
_t = nil
|
36
36
|
schema_hash[:desc] = _t if (_t = param_descs[name]).present?
|
@@ -38,16 +38,16 @@ module OpenApi
|
|
38
38
|
|
39
39
|
param_obj = ParamObj.new(name, param_type, type, required, schema_hash)
|
40
40
|
# The definition of the same name parameter will be overwritten
|
41
|
-
index = self[:parameters].map { |p| p.processed[:name] if p.is_a?
|
41
|
+
index = self[:parameters].map { |p| p.processed[:name] if p.is_a?(ParamObj) }.index name
|
42
42
|
index.present? ? self[:parameters][index] = param_obj : self[:parameters] << param_obj
|
43
43
|
end
|
44
44
|
|
45
|
-
#
|
45
|
+
# For supporting this: (just like `form '', data: { }` usage)
|
46
46
|
# do_query by: {
|
47
47
|
# :search_type => { type: String },
|
48
48
|
# :export! => { type: Boolean }
|
49
49
|
# }
|
50
|
-
%i[header header! path path! query query! cookie cookie!].each do |param_type|
|
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
53
|
args = [ key.dup.to_s.delete('!').to_sym, value.delete(:type), value ]
|
@@ -56,7 +56,12 @@ module OpenApi
|
|
56
56
|
end unless param_type.to_s['!']
|
57
57
|
end
|
58
58
|
|
59
|
-
def _param_agent name, type,
|
59
|
+
def _param_agent name, type = nil, one_of: nil, all_of: nil, any_of: nil, not: nil, **schema_hash
|
60
|
+
(schema_hash = type) and (type = type.delete(:type)) if type.is_a?(Hash) && type.key?(:type)
|
61
|
+
type = schema_hash[:type] if type.nil?
|
62
|
+
|
63
|
+
combined_schema = one_of || all_of || any_of || (_not = binding.local_variable_get(:not))
|
64
|
+
schema_hash = CombinedSchema.new(one_of: one_of, all_of: all_of, any_of: any_of, _not: _not) if combined_schema
|
60
65
|
param "#{@param_type}".delete('!'), name, type, (@param_type['!'] ? :req : :opt), schema_hash
|
61
66
|
end
|
62
67
|
|
@@ -76,9 +81,9 @@ module OpenApi
|
|
76
81
|
self[:requestBody] = RefObj.new(:requestBody, component_key).process
|
77
82
|
end
|
78
83
|
|
79
|
-
def
|
84
|
+
def merge_to_resp code, by:
|
80
85
|
_response = self[:responses].fetch(code)
|
81
|
-
self[:responses][code] = _response.override(
|
86
|
+
self[:responses][code] = _response.override(by).process
|
82
87
|
end
|
83
88
|
|
84
89
|
def response_ref code_compkey_hash
|
@@ -105,33 +110,31 @@ module OpenApi
|
|
105
110
|
body! media_type, desc, hash
|
106
111
|
end
|
107
112
|
|
108
|
-
def
|
109
|
-
self[:security] << { scheme_name =>
|
113
|
+
def security_require scheme_name, scopes: [ ]
|
114
|
+
self[:security] << { scheme_name => scopes }
|
110
115
|
end
|
111
116
|
|
117
|
+
alias security security_require
|
118
|
+
alias auth security_require
|
119
|
+
alias need_auth security_require
|
120
|
+
|
112
121
|
def server url, desc
|
113
122
|
self[:servers] << { url: url, description: desc }
|
114
123
|
end
|
115
124
|
|
116
125
|
def order *param_names
|
117
126
|
self.param_order = param_names
|
127
|
+
self.param_use = param_order if param_use.blank?
|
128
|
+
self.param_skip = param_use - param_order
|
118
129
|
end
|
119
130
|
|
120
131
|
def param_examples exp_by = :all, examples_hash
|
121
132
|
_process_objs
|
122
133
|
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
134
|
self[:examples] = ExampleObj.new(examples_hash, exp_by).process
|
133
135
|
end
|
134
|
-
|
136
|
+
|
137
|
+
alias examples param_examples
|
135
138
|
|
136
139
|
|
137
140
|
def _process_objs
|
@@ -141,13 +144,13 @@ module OpenApi
|
|
141
144
|
|
142
145
|
# Parameters sorting
|
143
146
|
self[:parameters].clone.each do |p|
|
144
|
-
self[:parameters][param_order.index(p[:name])] = p
|
147
|
+
self[:parameters][param_order.index(p[:name]) || -1] = p
|
145
148
|
end if param_order.present?
|
146
149
|
|
147
150
|
self[:responses]&.each do |code, obj|
|
148
151
|
self[:responses][code] = obj.process if obj.is_a?(ResponseObj)
|
149
152
|
end
|
150
153
|
end
|
151
|
-
end
|
154
|
+
end
|
152
155
|
end
|
153
156
|
end
|