swagger_yard 0.4.4 → 1.0.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/README.md +202 -103
- data/lib/swagger_yard.rb +8 -4
- data/lib/swagger_yard/api_group.rb +106 -0
- data/lib/swagger_yard/authorization.rb +21 -18
- data/lib/swagger_yard/configuration.rb +6 -11
- data/lib/swagger_yard/example.rb +11 -0
- data/lib/swagger_yard/model.rb +16 -34
- data/lib/swagger_yard/openapi.rb +120 -0
- data/lib/swagger_yard/operation.rb +67 -50
- data/lib/swagger_yard/operation.rb.~9b471577ebed4e4ba6ed266566355dbe5990787d~ +161 -0
- data/lib/swagger_yard/parameter.rb +1 -18
- data/lib/swagger_yard/path_item.rb +21 -0
- data/lib/swagger_yard/property.rb +3 -16
- data/lib/swagger_yard/{resource_listing.rb → specification.rb} +21 -34
- data/lib/swagger_yard/swagger.rb +172 -4
- data/lib/swagger_yard/type.rb +20 -12
- data/lib/swagger_yard/type.rb.~master~ +34 -0
- data/lib/swagger_yard/type_parser.rb +10 -4
- data/lib/swagger_yard/version.rb +1 -1
- metadata +9 -5
- data/lib/swagger_yard/api.rb +0 -39
- data/lib/swagger_yard/api_declaration.rb +0 -72
@@ -0,0 +1,106 @@
|
|
1
|
+
module SwaggerYard
|
2
|
+
class Tag < Struct.new(:name, :description)
|
3
|
+
end
|
4
|
+
|
5
|
+
class Paths
|
6
|
+
attr_reader :path_items
|
7
|
+
|
8
|
+
def initialize(path_items)
|
9
|
+
@path_items = path_items
|
10
|
+
end
|
11
|
+
|
12
|
+
def paths
|
13
|
+
path_items.keys
|
14
|
+
end
|
15
|
+
|
16
|
+
def merge(other)
|
17
|
+
merged_items = {}
|
18
|
+
(paths + other.paths).uniq.each do |path|
|
19
|
+
merged_items[path] = (path_items[path] || PathItem.new) + (other.path_items[path] || PathItem.new)
|
20
|
+
end
|
21
|
+
Paths.new(merged_items)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class ApiGroup
|
26
|
+
attr_accessor :description, :resource
|
27
|
+
attr_reader :path_items, :authorizations, :class_name
|
28
|
+
|
29
|
+
def self.from_yard_object(yard_object)
|
30
|
+
new.add_yard_object(yard_object)
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize
|
34
|
+
@resource = nil
|
35
|
+
@path_items = {}
|
36
|
+
@authorizations = {}
|
37
|
+
end
|
38
|
+
|
39
|
+
def valid?
|
40
|
+
!@resource.nil?
|
41
|
+
end
|
42
|
+
|
43
|
+
def paths
|
44
|
+
Paths.new(path_items)
|
45
|
+
end
|
46
|
+
|
47
|
+
def tag
|
48
|
+
@tag ||= Tag.new(resource, description)
|
49
|
+
end
|
50
|
+
|
51
|
+
def add_yard_object(yard_object)
|
52
|
+
case yard_object.type
|
53
|
+
when :class # controller
|
54
|
+
add_info(yard_object)
|
55
|
+
if valid?
|
56
|
+
yard_object.children.each do |child_object|
|
57
|
+
add_yard_object(child_object)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
when :method # actions
|
61
|
+
add_path_item(yard_object)
|
62
|
+
end
|
63
|
+
self
|
64
|
+
end
|
65
|
+
|
66
|
+
def add_info(yard_object)
|
67
|
+
@description = yard_object.docstring
|
68
|
+
@class_name = yard_object.path
|
69
|
+
|
70
|
+
if tag = yard_object.tags.detect {|t| t.tag_name == "resource"}
|
71
|
+
@resource = tag.text
|
72
|
+
end
|
73
|
+
|
74
|
+
# we only have api_key auth, the value for now is always empty array
|
75
|
+
@authorizations = Hash[yard_object.tags.
|
76
|
+
select {|t| t.tag_name == "authorize_with"}.
|
77
|
+
map(&:text).uniq.
|
78
|
+
map {|k| [k, []]}]
|
79
|
+
end
|
80
|
+
|
81
|
+
def add_path_item(yard_object)
|
82
|
+
path = path_from_yard_object(yard_object)
|
83
|
+
|
84
|
+
return if path.nil?
|
85
|
+
|
86
|
+
path_item = (path_items[path] ||= PathItem.new(self))
|
87
|
+
path_item.add_operation(yard_object)
|
88
|
+
path
|
89
|
+
end
|
90
|
+
|
91
|
+
def path_from_yard_object(yard_object)
|
92
|
+
if tag = yard_object.tags.detect {|t| t.tag_name == "path"}
|
93
|
+
tag.text
|
94
|
+
elsif fn = SwaggerYard.config.path_discovery_function
|
95
|
+
begin
|
96
|
+
method, path = fn[yard_object]
|
97
|
+
yard_object.add_tag YARD::Tags::Tag.new("path", path, [method]) if path
|
98
|
+
path
|
99
|
+
rescue => e
|
100
|
+
SwaggerYard.log.warn e.message
|
101
|
+
nil
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -1,34 +1,37 @@
|
|
1
1
|
module SwaggerYard
|
2
2
|
class Authorization
|
3
|
-
attr_reader :
|
4
|
-
attr_writer :
|
3
|
+
attr_reader :type, :name, :description
|
4
|
+
attr_writer :id, :key
|
5
5
|
|
6
6
|
def self.from_yard_object(yard_object)
|
7
7
|
new(yard_object.types.first, yard_object.name, yard_object.text)
|
8
8
|
end
|
9
9
|
|
10
|
-
def initialize(type,
|
11
|
-
@type, @
|
10
|
+
def initialize(type, name, description)
|
11
|
+
@type, @name, @description = type, name, description
|
12
|
+
@key = nil
|
12
13
|
end
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
15
|
+
def key
|
16
|
+
return @key if @key
|
17
|
+
return nil unless @description
|
18
|
+
return nil unless @type =~ /api_?key|bearer/i
|
19
|
+
@key, @description = @description.split(' ', 2)
|
20
|
+
@key
|
17
21
|
end
|
18
22
|
|
19
|
-
def
|
20
|
-
|
21
|
-
when "api_key"
|
22
|
-
"apiKey"
|
23
|
-
when "basic_auth"
|
24
|
-
"basicAuth"
|
25
|
-
end
|
23
|
+
def id
|
24
|
+
@id ||= api_key_id || name
|
26
25
|
end
|
27
26
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
27
|
+
private
|
28
|
+
def api_key_id
|
29
|
+
case type
|
30
|
+
when /api_?key/i
|
31
|
+
[name, key].compact.join('_').downcase.gsub('-', '_')
|
32
|
+
else
|
33
|
+
nil
|
34
|
+
end
|
32
35
|
end
|
33
36
|
end
|
34
37
|
end
|
@@ -3,16 +3,19 @@ module SwaggerYard
|
|
3
3
|
attr_accessor :api_version, :api_base_path
|
4
4
|
attr_accessor :swagger_version
|
5
5
|
attr_accessor :title, :description
|
6
|
-
attr_accessor :enable, :reload
|
7
6
|
attr_accessor :controller_path, :model_path
|
8
7
|
attr_accessor :path_discovery_function
|
9
8
|
attr_accessor :security_definitions
|
10
9
|
|
10
|
+
# openapi-compatible names
|
11
|
+
alias openapi_version swagger_version
|
12
|
+
alias openapi_version= swagger_version=
|
13
|
+
alias security_schemes security_definitions
|
14
|
+
alias security_schemes= security_definitions=
|
15
|
+
|
11
16
|
def initialize
|
12
17
|
@swagger_version = "2.0"
|
13
18
|
@api_version = "0.1"
|
14
|
-
@enable = false
|
15
|
-
@reload = true
|
16
19
|
@title = "Configure title with SwaggerYard.config.title"
|
17
20
|
@description = "Configure description with SwaggerYard.config.description"
|
18
21
|
@security_definitions = {}
|
@@ -25,13 +28,5 @@ module SwaggerYard
|
|
25
28
|
end if mappings
|
26
29
|
@external_schema
|
27
30
|
end
|
28
|
-
|
29
|
-
def swagger_spec_base_path=(ignored)
|
30
|
-
warn "DEPRECATED: swagger_spec_base_path is no longer necessary."
|
31
|
-
end
|
32
|
-
|
33
|
-
def api_path=(ignored)
|
34
|
-
warn "DEPRECATED: api_path is no longer necessary."
|
35
|
-
end
|
36
31
|
end
|
37
32
|
end
|
data/lib/swagger_yard/model.rb
CHANGED
@@ -4,7 +4,8 @@ module SwaggerYard
|
|
4
4
|
# complex model object as defined by swagger schema
|
5
5
|
#
|
6
6
|
class Model
|
7
|
-
|
7
|
+
include Example
|
8
|
+
attr_reader :id, :discriminator, :inherits, :description, :properties
|
8
9
|
|
9
10
|
def self.from_yard_object(yard_object)
|
10
11
|
new.tap do |model|
|
@@ -31,6 +32,10 @@ module SwaggerYard
|
|
31
32
|
@id = Model.mangle(yard_object.path)
|
32
33
|
end
|
33
34
|
|
35
|
+
def property(key)
|
36
|
+
properties.detect {|prop| prop.name == key }
|
37
|
+
end
|
38
|
+
|
34
39
|
def parse_tags(tags)
|
35
40
|
tags.each do |tag|
|
36
41
|
case tag.tag_name
|
@@ -48,43 +53,20 @@ module SwaggerYard
|
|
48
53
|
end
|
49
54
|
when "inherits"
|
50
55
|
@inherits << tag.text
|
56
|
+
when "example"
|
57
|
+
if tag.name && !tag.name.empty?
|
58
|
+
if (prop = property(tag.name))
|
59
|
+
prop.example = tag.text
|
60
|
+
else
|
61
|
+
SwaggerYard.log.warn("no property '#{tag.name}' defined yet to which to attach example: #{value.inspect}")
|
62
|
+
end
|
63
|
+
else
|
64
|
+
self.example = tag.text
|
65
|
+
end
|
51
66
|
end
|
52
67
|
end
|
53
68
|
|
54
69
|
self
|
55
70
|
end
|
56
|
-
|
57
|
-
def inherits_references
|
58
|
-
@inherits.map { |name| Type.new(name).to_h }
|
59
|
-
end
|
60
|
-
|
61
|
-
def to_h
|
62
|
-
h = {}
|
63
|
-
|
64
|
-
if !@properties.empty? || @inherits.empty?
|
65
|
-
h["type"] = "object"
|
66
|
-
h["properties"] = Hash[@properties.map {|p| [p.name, p.to_h]}]
|
67
|
-
h["required"] = @properties.select(&:required?).map(&:name) if @properties.detect(&:required?)
|
68
|
-
end
|
69
|
-
|
70
|
-
h["discriminator"] = @discriminator if @discriminator
|
71
|
-
|
72
|
-
# Polymorphism
|
73
|
-
unless @inherits.empty?
|
74
|
-
all_of = inherits_references
|
75
|
-
all_of << h unless h.empty?
|
76
|
-
|
77
|
-
if all_of.length == 1 && @description.empty?
|
78
|
-
h.update(all_of.first)
|
79
|
-
else
|
80
|
-
h = { "allOf" => all_of }
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
# Description
|
85
|
-
h["description"] = @description unless @description.empty?
|
86
|
-
|
87
|
-
h
|
88
|
-
end
|
89
71
|
end
|
90
72
|
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module SwaggerYard
|
2
|
+
class OpenAPI < Swagger
|
3
|
+
def to_h
|
4
|
+
metadata.merge(definitions)
|
5
|
+
end
|
6
|
+
|
7
|
+
def model_path
|
8
|
+
'#/components/schemas/'
|
9
|
+
end
|
10
|
+
|
11
|
+
def metadata
|
12
|
+
{
|
13
|
+
'openapi' => '3.0.0',
|
14
|
+
'info' => Info.new.to_h,
|
15
|
+
'servers' => [{'url' => SwaggerYard.config.api_base_path}]
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def definitions
|
20
|
+
{
|
21
|
+
"paths" => paths(specification.path_objects),
|
22
|
+
"tags" => tags(specification.tag_objects),
|
23
|
+
"components" => components
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def components
|
28
|
+
{
|
29
|
+
"schemas" => models(specification.model_objects),
|
30
|
+
"securitySchemes" => security_defs(specification.security_objects)
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
def parameters(params)
|
35
|
+
params.select { |param| param.param_type != 'body' }.map do |param|
|
36
|
+
{ "name" => param.name,
|
37
|
+
"description" => param.description,
|
38
|
+
"required" => param.required,
|
39
|
+
"in" => param.param_type
|
40
|
+
}.tap do |h|
|
41
|
+
schema = param.type.schema_with(model_path: model_path)
|
42
|
+
h["schema"] = schema
|
43
|
+
h["explode"] = true if !Array(param.allow_multiple).empty? && schema["items"]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def operation(op)
|
49
|
+
op_hash = super
|
50
|
+
if body_param = op.parameters.detect { |p| p.param_type == 'body' }
|
51
|
+
op_hash['requestBody'] = {
|
52
|
+
'description' => body_param.description,
|
53
|
+
'content' => {
|
54
|
+
'application/json' => {
|
55
|
+
'schema' => body_param.type.schema_with(model_path: model_path)
|
56
|
+
}
|
57
|
+
}
|
58
|
+
}
|
59
|
+
end
|
60
|
+
op_hash
|
61
|
+
end
|
62
|
+
|
63
|
+
def response(resp, op)
|
64
|
+
{}.tap do |h|
|
65
|
+
h['description'] = resp && resp.description || op.summary || ''
|
66
|
+
if resp && resp.type && (schema = resp.type.schema_with(model_path: model_path))
|
67
|
+
h['content'] = { 'application/json' => { 'schema' => schema } }
|
68
|
+
h['content']['application/json']['example'] = resp.example if resp.example
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def security_defs(security_objects)
|
74
|
+
defs = super
|
75
|
+
Hash[defs.map do |name, d|
|
76
|
+
[name, map_security(d)]
|
77
|
+
end]
|
78
|
+
end
|
79
|
+
|
80
|
+
def security(obj)
|
81
|
+
case obj.type
|
82
|
+
when /api_?key/i
|
83
|
+
{ 'type' => 'apiKey', 'name' => obj.key, 'in' => obj.name }
|
84
|
+
when /bearer/i
|
85
|
+
{ 'type' => obj.type, 'name' => obj.name, 'format' => obj.key }
|
86
|
+
else
|
87
|
+
{ 'type' => obj.type, 'name' => obj.name }
|
88
|
+
end.tap do |result|
|
89
|
+
result['description'] = obj.description if obj.description && !obj.description.empty?
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def map_security(h)
|
94
|
+
h = Hash[h.map { |k, v| [k.to_s, v] }] # quick-n-dirty stringify keys
|
95
|
+
case type = h['type'].to_s
|
96
|
+
when 'apiKey', 'http'
|
97
|
+
h
|
98
|
+
when 'oauth2'
|
99
|
+
# convert from swagger2-style oauth2
|
100
|
+
if (authUrl = h.delete('authorizationUrl')) && (flow = h.delete('flow'))
|
101
|
+
{ 'type' => 'oauth2', 'flows' => {
|
102
|
+
flow => { 'authorizationUrl' => authUrl } } }.tap do |result|
|
103
|
+
(h.keys - ['type']).each do |t|
|
104
|
+
result['flows'][flow][t] = h[t]
|
105
|
+
end
|
106
|
+
result['flows'][flow]['scopes'] = {} unless result['flows'][flow]['scopes']
|
107
|
+
end
|
108
|
+
else
|
109
|
+
h
|
110
|
+
end
|
111
|
+
else
|
112
|
+
{ 'type' => 'http', 'scheme' => type }.tap do |result|
|
113
|
+
result['bearerFormat'] = h['format'] if h['format']
|
114
|
+
end
|
115
|
+
end.tap do |result|
|
116
|
+
result['description'] = h['description'] unless h['description'].nil? || h['description'].empty?
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -1,13 +1,19 @@
|
|
1
1
|
module SwaggerYard
|
2
|
+
class Response
|
3
|
+
include Example
|
4
|
+
attr_accessor :status, :description, :type
|
5
|
+
end
|
6
|
+
|
2
7
|
class Operation
|
3
8
|
attr_accessor :description, :ruby_method
|
4
9
|
attr_writer :summary
|
5
|
-
attr_reader :path, :http_method
|
6
|
-
attr_reader :parameters
|
10
|
+
attr_reader :path, :http_method
|
11
|
+
attr_reader :parameters
|
12
|
+
attr_reader :path_item, :responses
|
7
13
|
|
8
14
|
# TODO: extract to operation builder?
|
9
|
-
def self.from_yard_object(yard_object,
|
10
|
-
new(
|
15
|
+
def self.from_yard_object(yard_object, path_item)
|
16
|
+
new(path_item).tap do |operation|
|
11
17
|
operation.ruby_method = yard_object.name(false)
|
12
18
|
operation.description = yard_object.docstring
|
13
19
|
yard_object.tags.each do |tag|
|
@@ -20,10 +26,16 @@ module SwaggerYard
|
|
20
26
|
when "response_type"
|
21
27
|
tag = SwaggerYard.requires_type(tag)
|
22
28
|
operation.add_response_type(Type.from_type_list(tag.types), tag.text) if tag
|
23
|
-
when "error_message"
|
24
|
-
operation.
|
29
|
+
when "error_message", "response"
|
30
|
+
operation.add_response(tag)
|
25
31
|
when "summary"
|
26
32
|
operation.summary = tag.text
|
33
|
+
when "example"
|
34
|
+
if tag.name && !tag.name.empty?
|
35
|
+
operation.response(tag.name).example = tag.text
|
36
|
+
else
|
37
|
+
operation.default_response.example = tag.text
|
38
|
+
end
|
27
39
|
end
|
28
40
|
end
|
29
41
|
|
@@ -31,57 +43,47 @@ module SwaggerYard
|
|
31
43
|
end
|
32
44
|
end
|
33
45
|
|
34
|
-
def initialize(
|
35
|
-
@
|
46
|
+
def initialize(path_item)
|
47
|
+
@path_item = path_item
|
36
48
|
@summary = nil
|
37
49
|
@description = ""
|
38
50
|
@parameters = []
|
39
|
-
@
|
40
|
-
@
|
51
|
+
@default_response = nil
|
52
|
+
@responses = []
|
41
53
|
end
|
42
54
|
|
43
55
|
def summary
|
44
56
|
@summary || description.split("\n\n").first || ""
|
45
57
|
end
|
46
58
|
|
47
|
-
def
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
if response_type
|
52
|
-
responses["default"]["schema"] = response_type.to_h
|
53
|
-
end
|
54
|
-
|
55
|
-
unless error_messages.empty?
|
56
|
-
error_messages.each do |err|
|
57
|
-
responses[err["code"].to_s] = {}.tap do |h|
|
58
|
-
h["description"] = err["message"]
|
59
|
-
h["schema"] = Type.from_type_list(Array(err["responseModel"])).to_h if err["responseModel"]
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
59
|
+
def operation_id
|
60
|
+
"#{api_group.resource}-#{ruby_method}"
|
61
|
+
end
|
63
62
|
|
64
|
-
|
63
|
+
def api_group
|
64
|
+
path_item.api_group
|
65
|
+
end
|
65
66
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
"parameters" => params,
|
70
|
-
"responses" => responses,
|
71
|
-
}.tap do |h|
|
72
|
-
h["description"] = description unless description.empty?
|
73
|
-
h["summary"] = summary unless summary.empty?
|
67
|
+
def tags
|
68
|
+
[api_group.resource].compact
|
69
|
+
end
|
74
70
|
|
75
|
-
|
76
|
-
|
77
|
-
|
71
|
+
def responses_by_status
|
72
|
+
{}.tap do |hash|
|
73
|
+
hash['default'] = default_response if @default_response || @responses.empty?
|
74
|
+
responses.each do |response|
|
75
|
+
hash[response.status] = response
|
78
76
|
end
|
77
|
+
end
|
78
|
+
end
|
79
79
|
|
80
|
+
def extended_attributes
|
81
|
+
{}.tap do |h|
|
80
82
|
# Rails controller/action: if constantize/controller_path methods are
|
81
83
|
# unavailable or constant is not defined, catch exception and skip these
|
82
84
|
# attributes.
|
83
85
|
begin
|
84
|
-
h["x-controller"] =
|
86
|
+
h["x-controller"] = api_group.class_name.constantize.controller_path.to_s
|
85
87
|
h["x-action"] = ruby_method.to_s
|
86
88
|
rescue NameError, NoMethodError
|
87
89
|
end
|
@@ -112,7 +114,7 @@ module SwaggerYard
|
|
112
114
|
# Example: [Array] status(required, body) Filter by status. (e.g. status[]=1&status[]=2&status[]=3)
|
113
115
|
# Example: [Integer] media[media_type_id] ID of the desired media type.
|
114
116
|
def add_parameter(tag)
|
115
|
-
param = Parameter.from_yard_tag(tag
|
117
|
+
param = Parameter.from_yard_tag(tag)
|
116
118
|
add_or_update_parameter param if param
|
117
119
|
end
|
118
120
|
|
@@ -124,29 +126,44 @@ module SwaggerYard
|
|
124
126
|
existing.allow_multiple = parameter.allow_multiple
|
125
127
|
elsif parameter.param_type == 'body' && @parameters.detect {|param| param.param_type == 'body'}
|
126
128
|
SwaggerYard.log.warn 'multiple body parameters invalid: ' \
|
127
|
-
"ignored #{parameter.name} for #{@
|
129
|
+
"ignored #{parameter.name} for #{@path_item.api_group.class_name}##{ruby_method}"
|
128
130
|
else
|
129
131
|
@parameters << parameter
|
130
132
|
end
|
131
133
|
end
|
132
134
|
|
135
|
+
def default_response
|
136
|
+
@default_response ||= Response.new.tap do |r|
|
137
|
+
r.status = 'default'
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
133
141
|
##
|
134
142
|
# Example:
|
135
143
|
# @response_type [Ownership] the requested ownership
|
136
144
|
def add_response_type(type, desc)
|
137
|
-
|
138
|
-
|
139
|
-
|
145
|
+
default_response.type = type
|
146
|
+
default_response.description = desc
|
147
|
+
end
|
148
|
+
|
149
|
+
def response(name)
|
150
|
+
status = Integer(name)
|
151
|
+
resp = responses.detect { |r| r.status == status }
|
152
|
+
unless resp
|
153
|
+
resp = Response.new
|
154
|
+
resp.status = status
|
155
|
+
responses << resp
|
156
|
+
end
|
157
|
+
resp
|
140
158
|
end
|
141
159
|
|
142
|
-
def
|
160
|
+
def add_response(tag)
|
143
161
|
tag = SwaggerYard.requires_name(tag)
|
144
162
|
return unless tag
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
}.reject {|_,v| v.nil?}
|
163
|
+
r = response(tag.name)
|
164
|
+
r.description = tag.text if tag.text
|
165
|
+
r.type = Type.from_type_list(Array(tag.types)) if tag.types
|
166
|
+
r
|
150
167
|
end
|
151
168
|
|
152
169
|
def sort_parameters
|