swagger_yard 0.4.4 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|