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
@@ -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
|