swaggard 0.5.4 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: ecc218894a718358cb55794cbe31c0506761fcca
4
- data.tar.gz: b45ab232037649703f9b277be8104a2c9df607de
2
+ SHA256:
3
+ metadata.gz: fc7e15b774e8c15ddf109dfaad4d2b3dd571e6a5527b8d237bcc89e07d5c867f
4
+ data.tar.gz: 1889e419f45111899943019b967ecb1ce094331a23b0fcc79fabd7e387c6e7ad
5
5
  SHA512:
6
- metadata.gz: 0bdc966974f63e4b95560412522ba5fe587df415a673bc94214bb3188cea43d8439748bbd5604f7b60ccd4353e949b7d569e46fb28c9c44fd15060a6e57b2301
7
- data.tar.gz: 9b3ec08e20ff8c589ec17fe6abc9f174791acb706648c4647133828313c8f2e0e54fe3ce5af416583ed51157bebd81aacbf2d7bec88e0c5c6494bb9b20ffdfbc
6
+ metadata.gz: 4f86d35cd4362a15b4a93bd9ac2ae4ce4a78d6bdca04823a9b3a8636903c35472944c73cde67d5e8c5ac5c05cf0c85c32aa7a25788173a52487e13aef184cc4c
7
+ data.tar.gz: 452ef43b35624b738762e396758dabf284b265d8e81fb0ef15f8e6ce07306a0c2a9204d1c1efc5a59028753c46aba6239d8912a988f7e7f7071d1772fcc30934
@@ -2,7 +2,6 @@ require_relative 'swagger/path'
2
2
 
3
3
  module Swaggard
4
4
  class ApiDefinition
5
-
6
5
  attr_accessor :definitions
7
6
 
8
7
  def initialize
@@ -23,32 +22,45 @@ module Swaggard
23
22
  @definitions.concat(operation.definitions)
24
23
  end
25
24
 
25
+ def ignore_put_if_patch!
26
+ @paths.values.each(&:ignore_put_if_patch!)
27
+ end
28
+
26
29
  def to_doc
30
+ contact = { 'name' => Swaggard.configuration.contact_name }
31
+ contact['email'] = Swaggard.configuration.contact_email if Swaggard.configuration.contact_email.present?
32
+ contact['url'] = Swaggard.configuration.contact_url if Swaggard.configuration.contact_url.present?
33
+
34
+ license = { 'name' => Swaggard.configuration.license_name }
35
+ license['url'] = Swaggard.configuration.license_url if Swaggard.configuration.license_url.present?
36
+
27
37
  {
28
38
  'swagger' => Swaggard.configuration.swagger_version,
29
39
  'info' => {
30
- 'description' => Swaggard.configuration.description,
31
40
  'version' => Swaggard.configuration.api_version,
32
41
  'title' => Swaggard.configuration.title,
42
+ 'description' => Swaggard.configuration.description,
33
43
  'termsOfService' => Swaggard.configuration.tos,
34
- 'contact' => {
35
- 'name' => Swaggard.configuration.contact_name,
36
- 'email' => Swaggard.configuration.contact_email,
37
- 'url' => Swaggard.configuration.contact_url
38
- },
39
- 'license' => {
40
- 'name' => Swaggard.configuration.license_name,
41
- 'url' => Swaggard.configuration.license_url
42
- }
44
+ 'contact' => contact,
45
+ 'license' => license,
43
46
  },
44
47
  'host' => Swaggard.configuration.host,
45
48
  'basePath' => Swaggard.configuration.api_base_path,
46
- 'tags' => @tags.map { |_, tag| tag.to_doc },
47
49
  'schemes' => Swaggard.configuration.schemes,
48
- 'paths' => Hash[@paths.values.map { |path| [path.path, path.to_doc] }],
50
+ 'consumes' => Swaggard.configuration.api_formats.map { |format| "application/#{format}" },
51
+ 'produces' => Swaggard.configuration.api_formats.map { |format| "application/#{format}" },
52
+ 'tags' => @tags.map { |_, tag| tag.to_doc },
53
+ 'paths' => Hash[@paths.values.map { |path| [format_path(path.path), path.to_doc] }],
49
54
  'definitions' => Hash[@definitions.map { |definition| [definition.id, definition.to_doc] }]
50
55
  }
51
56
  end
52
57
 
58
+ private
59
+
60
+ def format_path(path)
61
+ return path unless Swaggard.configuration.exclude_base_path_from_paths
62
+
63
+ path.gsub(Swaggard.configuration.api_base_path, '')
64
+ end
53
65
  end
54
- end
66
+ end
@@ -18,7 +18,12 @@ module Swaggard
18
18
  :description, :tos, :contact_email, :contact_name, :contact_url, :host,
19
19
  :authentication_type, :authentication_key, :authentication_value,
20
20
  :access_username, :access_password, :default_content_type, :use_cache,
21
- :language, :additional_parameters, :schemes
21
+ :language, :additional_parameters, :schemes, :ignore_undocumented_paths,
22
+ :license_name, :exclude_base_path_from_paths, :default_response_description,
23
+ :default_response_status_code, :excluded_paths, :path_parameter_description,
24
+ :ignore_put_if_patch_exists
25
+
26
+ attr_reader :custom_types
22
27
 
23
28
  def swagger_version
24
29
  @swagger_version ||= '2.0'
@@ -65,7 +70,7 @@ module Swaggard
65
70
  end
66
71
 
67
72
  def contact_url
68
- @contact_email ||= ''
73
+ @contact_url ||= ''
69
74
  end
70
75
 
71
76
  def license_name
@@ -100,6 +105,26 @@ module Swaggard
100
105
  @default_content_type ||= ''
101
106
  end
102
107
 
108
+ def default_response_status_code
109
+ @default_response_status_code ||= 'default'
110
+ end
111
+
112
+ def default_response_description
113
+ @default_response_description ||= 'successful operation'
114
+ end
115
+
116
+ def ignore_undocumented_paths
117
+ return @ignore_undocumented_paths unless @ignore_undocumented_paths.nil?
118
+
119
+ @ignore_undocumented_paths = false
120
+ end
121
+
122
+ def exclude_base_path_from_paths
123
+ return @exclude_base_path_from_paths unless @exclude_base_path_from_paths.nil?
124
+
125
+ @exclude_base_path_from_paths = false
126
+ end
127
+
103
128
  def language
104
129
  @language ||= 'en'
105
130
  end
@@ -112,5 +137,26 @@ module Swaggard
112
137
  @use_cache ||= false
113
138
  end
114
139
 
140
+ def custom_types
141
+ @custom_types ||= {}
142
+ end
143
+
144
+ def excluded_paths
145
+ @excluded_paths ||= []
146
+ end
147
+
148
+ def add_custom_type(name, definition)
149
+ custom_types[name] = definition
150
+ end
151
+
152
+ def path_parameter_description
153
+ @path_parameter_description ||= ->(path_parameter) { "Scope response to #{path_parameter.name}" }
154
+ end
155
+
156
+ def ignore_put_if_patch_exists
157
+ return @ignore_put_if_patch_exists unless @ignore_put_if_patch_exists.nil?
158
+
159
+ @ignore_put_if_patch_exists = false
160
+ end
115
161
  end
116
162
  end
@@ -3,23 +3,21 @@ require_relative '../swagger/tag'
3
3
 
4
4
  module Swaggard
5
5
  module Parsers
6
- class Controllers
6
+ class Controller
7
7
 
8
- def run(yard_objects, routes)
8
+ def run(yard_objects)
9
9
  tag = nil
10
- operations = []
10
+ operations = {}
11
11
 
12
12
  yard_objects.each do |yard_object|
13
13
  if yard_object.type == :class
14
14
  tag = Swagger::Tag.new(yard_object)
15
15
  elsif tag && yard_object.type == :method
16
- operation = Swagger::Operation.new(yard_object, tag, routes)
17
- operations << operation if operation.valid?
16
+ name = yard_object.name
17
+ operations[name.to_s] = yard_object
18
18
  end
19
19
  end
20
20
 
21
- return unless operations.any?
22
-
23
21
  return tag, operations
24
22
  end
25
23
 
@@ -5,21 +5,22 @@ module Swaggard
5
5
  def run(routes)
6
6
  return {} unless routes
7
7
 
8
- parsed_routes = {}
9
-
10
- routes.each do |route|
11
- controller = route_controller(route)
12
- action = route_action(route)
13
-
14
- parsed_routes[controller] ||= {}
15
- parsed_routes[controller][action] = {
16
- verb: route_verb(route),
17
- path: route_path(route),
18
- path_params: route_path_params(route)
19
- }
20
- end
21
-
22
- parsed_routes
8
+ routes.inject({}) do |parsed_routes, route|
9
+ path = route_path(route)
10
+
11
+ unless Swaggard.configuration.excluded_paths.any? { |excluded_path| Regexp.new(excluded_path) =~ path }
12
+ verb = route_verb(route)
13
+
14
+ parsed_routes[path] ||= {}
15
+ parsed_routes[path][verb] = {
16
+ controller: route_controller(route),
17
+ action: route_action(route),
18
+ path_params: route_path_params(route)
19
+ }
20
+ end
21
+
22
+ parsed_routes
23
+ end.sort.to_h
23
24
  end
24
25
 
25
26
  private
@@ -3,24 +3,37 @@ module Swaggard
3
3
  class Definition
4
4
 
5
5
  attr_reader :id
6
+ attr_writer :description, :title
6
7
 
7
8
  def initialize(id)
8
9
  @id = id
10
+ @title = ''
9
11
  @properties = []
12
+ @description = ''
10
13
  end
11
14
 
12
15
  def add_property(property)
13
16
  @properties << property
14
17
  end
15
18
 
19
+ def empty?
20
+ @properties.empty?
21
+ end
22
+
16
23
  def to_doc
17
- {
18
- 'type' => 'object',
19
- 'required' => [],
20
- 'properties' => Hash[@properties.map { |property| [property.id, property.to_doc] }]
21
- }
24
+ {}.tap do |doc|
25
+ doc['title'] = @title if @title.present?
26
+ doc['type'] = 'object'
27
+
28
+ doc['description'] = @description if @description.present?
29
+
30
+ doc['properties'] = Hash[@properties.map { |property| [property.id, property.to_doc] }]
31
+ required_properties = @properties.select(&:required?).map(&:id)
32
+ doc['required'] = required_properties if required_properties.any?
33
+ end
34
+
22
35
  end
23
36
 
24
37
  end
25
38
  end
26
- end
39
+ end
@@ -9,28 +9,42 @@ require_relative 'response'
9
9
  module Swaggard
10
10
  module Swagger
11
11
  class Operation
12
-
13
12
  attr_reader :nickname
14
13
  attr_accessor :path, :parameters, :description, :http_method, :summary, :notes,
15
14
  :error_responses, :tag
16
15
 
17
- def initialize(yard_object, tag, routes)
16
+ def initialize(yard_object, tag, path, verb, path_params)
18
17
  @name = yard_object.name
19
18
  @tag = tag
20
- @summary = yard_object.docstring.lines.first || ''
19
+ @summary = (yard_object.docstring.lines.first || '').chomp
21
20
  @parameters = []
22
21
  @responses = []
23
- @description = (yard_object.docstring.lines[1..-1] || []).join("\n")
22
+
23
+ @description = (yard_object.docstring.lines[1..-1] || []).map(&:chomp).reject(&:empty?).compact.join("\n")
24
24
  @formats = Swaggard.configuration.api_formats
25
+ @http_method = verb
26
+ @path = path
27
+
28
+ build_path_parameters(path_params)
25
29
 
26
30
  yard_object.tags.each do |yard_tag|
27
31
  value = yard_tag.text
28
32
 
29
33
  case yard_tag.tag_name
34
+ when 'operation_id'
35
+ @operation_id = value
30
36
  when 'query_parameter'
31
37
  @parameters << Parameters::Query.new(value)
32
38
  when 'form_parameter'
33
39
  @parameters << Parameters::Form.new(value)
40
+ when 'body_required'
41
+ body_parameter.is_required = true
42
+ when 'body_description'
43
+ body_parameter.description = value
44
+ when 'body_title'
45
+ body_parameter.title = value
46
+ when 'body_definition'
47
+ body_parameter.definition = value
34
48
  when 'body_parameter'
35
49
  body_parameter.add_property(value)
36
50
  when 'parameter_list'
@@ -41,11 +55,15 @@ module Swaggard
41
55
  success_response.status_code = value
42
56
  when 'response_root'
43
57
  success_response.response_root = value
58
+ when 'response_description'
59
+ success_response.description = value
60
+ when 'response_example'
61
+ success_response.add_example(value)
62
+ when 'response_header'
63
+ success_response.add_header(value)
44
64
  end
45
65
  end
46
66
 
47
- build_path_parameters(routes)
48
-
49
67
  @parameters.sort_by { |parameter| parameter.name }
50
68
 
51
69
  @responses << success_response
@@ -55,20 +73,22 @@ module Swaggard
55
73
  @path.present?
56
74
  end
57
75
 
58
- def to_doc
59
- produces = @formats.map { |format| "application/#{format}" }
60
- consumes = @formats.map { |format| "application/#{format}" }
76
+ def empty?
77
+ @summary.blank? && @description.blank?
78
+ end
61
79
 
80
+ def to_doc
62
81
  {
63
82
  'tags' => [@tag.name],
83
+ 'operationId' => @operation_id || @name,
64
84
  'summary' => @summary,
65
85
  'description' => @description,
66
- 'operationId' => @name,
67
- 'consumes' => consumes,
68
- 'produces' => produces,
69
- 'parameters' => @parameters.map(&:to_doc),
70
- 'responses' => Hash[@responses.map { |response| [response.status_code, response.to_doc] }]
71
- }
86
+ 'produces' => @formats.map { |format| "application/#{format}" },
87
+ }.tap do |doc|
88
+ doc['consumes'] = @formats.map { |format| "application/#{format}" } if @body_parameter
89
+ doc['parameters'] = @parameters.map(&:to_doc)
90
+ doc['responses'] = Hash[@responses.map { |response| [response.status_code, response.to_doc] }]
91
+ end
72
92
  end
73
93
 
74
94
  def definitions
@@ -92,19 +112,11 @@ module Swaggard
92
112
  @body_parameter
93
113
  end
94
114
 
95
- def build_path_parameters(routes)
115
+ def build_path_parameters(path_params)
96
116
  return unless @tag.controller_class
97
117
 
98
- route = (routes[@tag.controller_class.controller_path] || {})[@name.to_s]
99
-
100
- return unless route
101
-
102
- @http_method = route[:verb]
103
- @path = route[:path]
104
-
105
- route[:path_params].each { |path_param| @parameters << Parameters::Path.new(path_param) }
118
+ path_params.each { |path_param| @parameters << Parameters::Path.new(self, path_param) }
106
119
  end
107
-
108
120
  end
109
121
  end
110
122
  end
@@ -3,19 +3,20 @@ module Swaggard
3
3
  module Parameters
4
4
  class Base
5
5
 
6
- attr_reader :name
6
+ attr_reader :name, :description
7
+ attr_writer :is_required
7
8
 
8
9
  def to_doc
9
10
  {
10
- 'in' => @in,
11
11
  'name' => @name,
12
- 'description' => @description,
12
+ 'in' => @in,
13
13
  'required' => @is_required,
14
- 'type' => @data_type
14
+ 'type' => @data_type,
15
+ 'description' => description
15
16
  }
16
17
  end
17
18
 
18
19
  end
19
20
  end
20
21
  end
21
- end
22
+ end
@@ -9,10 +9,12 @@ module Swaggard
9
9
  attr_reader :definition
10
10
 
11
11
  def initialize(operation_name)
12
- @in = 'body'
13
- @name = 'body'
14
- @description = ''
15
- @definition = Definition.new("#{operation_name}_body")
12
+ @in = 'body'
13
+ @name = 'body'
14
+ @is_required = false
15
+ @description = ''
16
+ @definition = Definition.new("#{operation_name}_body")
17
+ @definition_id = @definition.id
16
18
  end
17
19
 
18
20
  def add_property(string)
@@ -20,17 +22,33 @@ module Swaggard
20
22
  @definition.add_property(property)
21
23
  end
22
24
 
25
+ def empty?
26
+ @definition.empty?
27
+ end
28
+
23
29
  def to_doc
24
30
  doc = super
25
31
 
26
32
  doc.delete('type')
33
+ doc.delete('description')
27
34
 
28
- doc['required'] = false
29
- doc['schema'] = { '$ref' => "#/definitions/#{@definition.id}" }
35
+ doc['schema'] = { '$ref' => "#/definitions/#{@definition_id}" }
30
36
 
31
37
  doc
32
38
  end
33
39
 
40
+ def description=(description)
41
+ @definition.description = description
42
+ end
43
+
44
+ def title=(title)
45
+ @definition.title = title
46
+ end
47
+
48
+ def definition=(definition)
49
+ @definition_id = definition
50
+ end
51
+
34
52
  private
35
53
 
36
54
 
@@ -42,9 +60,14 @@ module Swaggard
42
60
  parse(string)
43
61
  end
44
62
 
63
+ def required?
64
+ @required
65
+ end
66
+
45
67
  def to_doc
46
68
  result = @type.to_doc
47
69
  result['description'] = @description if @description
70
+ result['enum'] = @options if @options.present?
48
71
  result
49
72
  end
50
73
 
@@ -52,13 +75,16 @@ module Swaggard
52
75
  # Example: [Array] status(required) Filter by status. (e.g. status[]=1&status[]=2&status[]=3)
53
76
  # Example: [Integer] media[media_type_id] ID of the desired media type.
54
77
  def parse(string)
55
- data_type, name, description = string.split
56
-
57
- data_type.gsub!('[', '').gsub!(']', '')
78
+ data_type, required, name, options_and_description = string.match(/\A\[(\S*)\](!)?\s*([\w\[\]]*)\s*(.*)\Z/).captures
79
+ allow_multiple = name.gsub!('[]', '')
80
+ options, description = options_and_description.match(/\A(\[.*\])?(.*)\Z/).captures
81
+ options = options ? options.gsub(/\[?\]?\s?/, '').split(',') : []
58
82
 
59
83
  @id = name
60
- @description = description
84
+ @description = description if description.present?
61
85
  @type = Type.new([data_type])
86
+ @required = required
87
+ @options = options
62
88
  end
63
89
 
64
90
  end
@@ -29,4 +29,4 @@ module Swaggard
29
29
  end
30
30
  end
31
31
  end
32
- end
32
+ end
@@ -4,7 +4,6 @@ module Swaggard
4
4
  module Swagger
5
5
  module Parameters
6
6
  class List < Base
7
-
8
7
  def initialize(string)
9
8
  @in = 'query'
10
9
  parse(string)
@@ -39,8 +38,7 @@ module Swaggard
39
38
  @data_type = data_type.downcase
40
39
  @is_required = required.present?
41
40
  end
42
-
43
41
  end
44
42
  end
45
43
  end
46
- end
44
+ end
@@ -5,17 +5,30 @@ module Swaggard
5
5
  module Parameters
6
6
  class Path < Base
7
7
 
8
- attr_reader :name
8
+ attr_reader :operation, :name
9
9
 
10
- def initialize(param_name)
10
+ def initialize(operation, param_name)
11
+ @operation = operation
11
12
  @in = 'path'
12
13
  @name = param_name.to_s
13
14
  @data_type = 'string'
14
15
  @is_required = true
15
- @description = "Scope response to #{@name}"
16
16
  end
17
17
 
18
+ def description
19
+ @description ||= get_description
20
+ end
21
+
22
+ private
23
+
24
+ def get_description
25
+ if Swaggard.configuration.path_parameter_description.respond_to?(:call)
26
+ Swaggard.configuration.path_parameter_description.call(self)
27
+ else
28
+ Swaggard.configuration.path_parameter_description
29
+ end
30
+ end
18
31
  end
19
32
  end
20
33
  end
21
- end
34
+ end
@@ -11,11 +11,16 @@ module Swaggard
11
11
  end
12
12
 
13
13
  def to_doc
14
- doc = super
15
-
16
- doc.merge!('enum' => @options) if @options.any?
17
-
18
- doc
14
+ {
15
+ 'name' => @name,
16
+ 'in' => @in,
17
+ 'required' => @is_required,
18
+ }.tap do |doc|
19
+ doc.merge!(@type.to_doc)
20
+
21
+ doc.merge!('enum' => @options) if @options.any?
22
+ doc.merge!('description' => description)
23
+ end
19
24
  end
20
25
 
21
26
  private
@@ -24,7 +29,7 @@ module Swaggard
24
29
  # Example: [Array] status(required) Filter by status. (e.g. status[]=1&status[]=2&status[]=3)
25
30
  # Example: [Integer] media[media_type_id] ID of the desired media type.
26
31
  def parse(string)
27
- data_type, name, required, options_and_description = string.match(/\A\[(\S*)\]\s*([\w\[\]]*)(\(required\))?\s*(.*)\Z/).captures
32
+ data_type, required, name, options_and_description = string.match(/\A\[(\S*)\](!)?\s*([\w\[\]]*)\s*(.*)\Z/).captures
28
33
  allow_multiple = name.gsub!('[]', '')
29
34
 
30
35
  options, description = options_and_description.match(/\A(\[.*\])?(.*)\Z/).captures
@@ -32,7 +37,7 @@ module Swaggard
32
37
 
33
38
  @name = name
34
39
  @description = description
35
- @data_type = data_type.downcase
40
+ @type = Type.new([data_type])
36
41
  @is_required = required.present?
37
42
  @allow_multiple = allow_multiple.present?
38
43
  @options = options
@@ -41,4 +46,4 @@ module Swaggard
41
46
  end
42
47
  end
43
48
  end
44
- end
49
+ end
@@ -3,7 +3,6 @@ require_relative 'operation'
3
3
  module Swaggard
4
4
  module Swagger
5
5
  class Path
6
-
7
6
  attr_reader :path
8
7
 
9
8
  def initialize(path)
@@ -15,10 +14,13 @@ module Swaggard
15
14
  @operations[operation.http_method.downcase] = operation
16
15
  end
17
16
 
17
+ def ignore_put_if_patch!
18
+ @operations.delete('put') if @operations.key?('patch')
19
+ end
20
+
18
21
  def to_doc
19
22
  Hash[@operations.map { |http_method, operation| [http_method, operation.to_doc] }]
20
23
  end
21
-
22
24
  end
23
25
  end
24
- end
26
+ end
@@ -7,14 +7,29 @@ module Swaggard
7
7
  attr_reader :id, :type, :description
8
8
 
9
9
  def initialize(yard_object)
10
- @id = yard_object.name
10
+ name = yard_object.name.dup
11
+ options_and_description = yard_object.text&.dup || ''
12
+
13
+ options, description = options_and_description.match(/\A(\[.*\])?(.*)\Z/).captures
14
+ options = options ? options.gsub(/\[?\]?\s?/, '').split(',') : []
15
+ description = description.strip
16
+ required = name.gsub!(/^!/, '')
17
+
18
+ @id = name
11
19
  @type = Type.new(yard_object.types)
12
- @description = yard_object.text
20
+ @description = description
21
+ @required = required.present?
22
+ @options = options
23
+ end
24
+
25
+ def required?
26
+ @required
13
27
  end
14
28
 
15
29
  def to_doc
16
30
  result = @type.to_doc
17
- result['description'] = @description if @description
31
+ result['description'] = @description if @description.present?
32
+ result['enum'] = @options if @options.present?
18
33
  result
19
34
  end
20
35
 
@@ -1,9 +1,9 @@
1
+ require_relative 'response_header'
2
+
1
3
  module Swaggard
2
4
  module Swagger
3
5
  class Response
4
6
 
5
- DEFAULT_STATUS_CODE = 'default'
6
- DEFAULT_DESCRIPTION = 'successful operation'
7
7
  PRIMITIVE_TYPES = %w[integer long float double string byte binary boolean date date-time password hash]
8
8
 
9
9
  attr_writer :status_code, :description
@@ -11,6 +11,7 @@ module Swaggard
11
11
  def initialize(operation_name)
12
12
  @operation_name = operation_name
13
13
  @response_model = ResponseModel.new
14
+ @headers = []
14
15
  end
15
16
 
16
17
  def definition
@@ -22,7 +23,7 @@ module Swaggard
22
23
  end
23
24
 
24
25
  def status_code
25
- @status_code || DEFAULT_STATUS_CODE
26
+ @status_code || Swaggard.configuration.default_response_status_code
26
27
  end
27
28
 
28
29
  def response_class=(value)
@@ -34,8 +35,16 @@ module Swaggard
34
35
  @response_model.id = root
35
36
  end
36
37
 
38
+ def add_example(value)
39
+ @example = value
40
+ end
41
+
42
+ def add_header(value)
43
+ @headers << ResponseHeader.new(value)
44
+ end
45
+
37
46
  def description
38
- @description || DEFAULT_DESCRIPTION
47
+ @description || Swaggard.configuration.default_response_description
39
48
  end
40
49
 
41
50
  def to_doc
@@ -47,6 +56,18 @@ module Swaggard
47
56
  end
48
57
 
49
58
  doc.merge!('schema' => schema) if schema
59
+ doc.merge!('examples' => example_to_doc) if @example
60
+ if @headers.any?
61
+ doc['headers'] = Hash[@headers.map { |header| [header.name, header.to_doc] }]
62
+ end
63
+ end
64
+ end
65
+
66
+ def example_to_doc
67
+ Swaggard.configuration.api_formats.inject({}) do |examples, format|
68
+ examples.merge!("application/#{format}" => { '$ref' => @example })
69
+
70
+ examples
50
71
  end
51
72
  end
52
73
 
@@ -0,0 +1,37 @@
1
+ module Swaggard
2
+ module Swagger
3
+ class ResponseHeader
4
+ attr_reader :name
5
+
6
+ def initialize(string)
7
+ parse(string)
8
+ end
9
+
10
+ def to_doc
11
+ {
12
+ 'description' => @description,
13
+ 'type' => @type,
14
+ }
15
+ end
16
+
17
+ private
18
+
19
+ # Example: [Array] status Filter by status. (e.g. status[]=1&status[]=2&status[]=3)
20
+ # Example: [Array]! status Filter by status. (e.g. status[]=1&status[]=2&status[]=3)
21
+ # Example: [Integer] media[media_type_id] ID of the desired media type.
22
+ def parse(string)
23
+ data_type, name, options_and_description = string.match(/\A\[(\S*)\]\s*([\w\-\[\]]*)\s*(.*)\Z/).captures
24
+ allow_multiple = name.gsub!('[]', '')
25
+
26
+ options, description = options_and_description.match(/\A(\[.*\])?(.*)\Z/).captures
27
+ options = options ? options.gsub(/\[?\]?\s?/, '').split(',') : []
28
+
29
+ @name = name
30
+ @description = description
31
+ @type = data_type
32
+ @allow_multiple = allow_multiple.present?
33
+ @options = options
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,7 +1,6 @@
1
1
  module Swaggard
2
2
  module Swagger
3
3
  class Type
4
-
5
4
  BASIC_TYPES = {
6
5
  'integer' => { 'type' => 'integer', 'format' => 'int32' },
7
6
  'long' => { 'type' => 'integer', 'format' => 'int64' },
@@ -31,10 +30,6 @@ module Swaggard
31
30
  end
32
31
  end
33
32
 
34
- def basic_type?
35
- BASIC_TYPES.has_key?(@name.downcase)
36
- end
37
-
38
33
  private
39
34
 
40
35
  def parse(types)
@@ -44,14 +39,24 @@ module Swaggard
44
39
  @is_array = parts.grep(/array/i).any?
45
40
  end
46
41
 
42
+
43
+ def basic_type?
44
+ BASIC_TYPES.has_key?(@name.downcase)
45
+ end
46
+
47
+ def custom_type?
48
+ Swaggard.configuration.custom_types.has_key?(@name)
49
+ end
50
+
47
51
  def type_tag_and_name
48
52
  if basic_type?
49
- BASIC_TYPES[@name.downcase]
53
+ BASIC_TYPES[@name.downcase].dup
54
+ elsif custom_type?
55
+ Swaggard.configuration.custom_types[@name].dup
50
56
  else
51
57
  { '$ref' => "#/definitions/#{name}" }
52
58
  end
53
59
  end
54
-
55
60
  end
56
61
  end
57
- end
62
+ end
@@ -1,5 +1,3 @@
1
1
  module Swaggard
2
-
3
- VERSION = '0.5.4'
4
-
2
+ VERSION = '1.0.0'
5
3
  end
data/lib/swaggard.rb CHANGED
@@ -5,7 +5,7 @@ require 'swaggard/api_definition'
5
5
  require 'swaggard/configuration'
6
6
  require 'swaggard/engine'
7
7
 
8
- require 'swaggard/parsers/controllers'
8
+ require 'swaggard/parsers/controller'
9
9
  require 'swaggard/parsers/models'
10
10
  require 'swaggard/parsers/routes'
11
11
 
@@ -22,22 +22,30 @@ module Swaggard
22
22
 
23
23
  # Register some custom yard tags
24
24
  def register_custom_yard_tags!
25
- ::YARD::Tags::Library.define_tag('Controller\'s tag', :tag)
25
+ ::YARD::Tags::Library.define_tag('Controller\'s tag', :tag)
26
+ ::YARD::Tags::Library.define_tag('Operation id', :operation_id)
26
27
  ::YARD::Tags::Library.define_tag('Query parameter', :query_parameter)
27
- ::YARD::Tags::Library.define_tag('Form parameter', :form_parameter)
28
- ::YARD::Tags::Library.define_tag('Body parameter', :body_parameter)
29
- ::YARD::Tags::Library.define_tag('Parameter list', :parameter_list)
30
- ::YARD::Tags::Library.define_tag('Response class', :response_class)
31
- ::YARD::Tags::Library.define_tag('Response Root', :response_root)
32
- ::YARD::Tags::Library.define_tag('Response Status', :response_status)
28
+ ::YARD::Tags::Library.define_tag('Form parameter', :form_parameter)
29
+ ::YARD::Tags::Library.define_tag('Body required', :body_required)
30
+ ::YARD::Tags::Library.define_tag('Body description', :body_description)
31
+ ::YARD::Tags::Library.define_tag('Body title', :body_title)
32
+ ::YARD::Tags::Library.define_tag('Body definition', :body_definition)
33
+ ::YARD::Tags::Library.define_tag('Body parameter', :body_parameter)
34
+ ::YARD::Tags::Library.define_tag('Parameter list', :parameter_list)
35
+ ::YARD::Tags::Library.define_tag('Response class', :response_class)
36
+ ::YARD::Tags::Library.define_tag('Response root', :response_root)
37
+ ::YARD::Tags::Library.define_tag('Response status', :response_status)
38
+ ::YARD::Tags::Library.define_tag('Response description', :response_description)
39
+ ::YARD::Tags::Library.define_tag('Response example', :response_example)
40
+ ::YARD::Tags::Library.define_tag('Response header', :response_header)
33
41
  end
34
42
 
35
- def get_doc(host)
43
+ def get_doc(host = nil)
36
44
  load!
37
45
 
38
46
  doc = @api.to_doc
39
47
 
40
- doc['host'] = host if doc['host'].blank?
48
+ doc['host'] = host if doc['host'].blank? && host
41
49
 
42
50
  doc
43
51
  end
@@ -48,22 +56,67 @@ module Swaggard
48
56
  @api = Swaggard::ApiDefinition.new
49
57
 
50
58
  parse_models
51
- parse_controllers
59
+ build_operations
60
+
61
+ @api.ignore_put_if_patch! if Swaggard.configuration.ignore_put_if_patch_exists
52
62
  end
53
63
 
54
- def parse_controllers
55
- parser = Parsers::Controllers.new
64
+ def build_operation(path, verb, route)
65
+ controller_name = route[:controller]
66
+ action_name = route[:action]
67
+
68
+ return unless controllers[controller_name]
69
+
70
+ controller_tag = controllers[controller_name][:tag]
71
+
72
+ return unless controller_tag
73
+
74
+ return unless controllers[controller_name][:operations]
75
+
76
+ return unless controllers[controller_name][:operations][action_name]
56
77
 
78
+ operation_yard_object = controllers[controller_name][:operations][action_name]
79
+
80
+ return unless operation_yard_object
81
+
82
+ operation = Swagger::Operation.new(operation_yard_object, controller_tag, path, verb, route[:path_params])
83
+
84
+ return unless operation.valid?
85
+ return if Swaggard.configuration.ignore_undocumented_paths && operation.empty?
86
+
87
+ return controller_tag, operation
88
+ end
89
+
90
+ def build_operations
91
+ routes.each do |path, verbs|
92
+ verbs.each do |verb, route_options|
93
+ tag, operation = build_operation(path, verb, route_options)
94
+
95
+ next unless tag
96
+
97
+ @api.add_tag(tag)
98
+ @api.add_operation(operation)
99
+ end
100
+ end
101
+ end
102
+
103
+ def controllers
104
+ return @controllers if @controllers
105
+
106
+ parser = Parsers::Controller.new
107
+
108
+ @controllers = {}
57
109
  Dir[configuration.controllers_path].each do |file|
58
110
  yard_objects = get_yard_objects(file)
59
111
 
60
- tag, operations = parser.run(yard_objects, routes)
112
+ tag, operations = parser.run(yard_objects)
61
113
 
62
114
  next unless tag
63
115
 
64
- @api.add_tag(tag)
65
- operations.each { |operation| @api.add_operation(operation) }
116
+ @controllers[tag.controller_class.controller_path] ||= { tag: tag, operations: operations }
66
117
  end
118
+
119
+ @controllers
67
120
  end
68
121
 
69
122
  def routes
@@ -6,4 +6,10 @@ namespace :swaggard do
6
6
  puts 'Swaggard cache has been cleared'
7
7
  end
8
8
 
9
- end
9
+ desc 'Dump swagger.json'
10
+ task :dump => :environment do
11
+ swagger = Swaggard.get_doc
12
+ File.open('public/swagger/swaggard.json', 'w') { |file| file.write(JSON.pretty_generate(swagger)) }
13
+ puts 'Swaggard dump complete'
14
+ end
15
+ end
@@ -1 +1 @@
1
- {"swagger":"2.0","info":{"description":"","version":"0.1","title":"","termsOfService":"","contact":{"name":"","email":"","url":""},"license":{"name":"","url":""}},"host":"localhost:3000","basePath":"/","tags":[{"name":"admin/pets","description":"This document describes the API for interacting with Pet resources"},{"name":"pets","description":"This document describes the API for interacting with Pet resources"}],"schemes":["https","http"],"paths":{"/admin/pets":{"get":{"tags":["admin/pets"],"summary":"return a list of Pets","description":"","operationId":"index","consumes":["application/xml","application/json"],"produces":["application/xml","application/json"],"parameters":[],"responses":{"default":{"description":"successful operation"}}}},"/pets":{"get":{"tags":["pets"],"summary":"return a list of Pets","description":"","operationId":"index","consumes":["application/xml","application/json"],"produces":["application/xml","application/json"],"parameters":[],"responses":{"default":{"description":"successful operation"}}}},"/pets/{id}":{"get":{"tags":["pets"],"summary":"return a Pet","description":"","operationId":"show","consumes":["application/xml","application/json"],"produces":["application/xml","application/json"],"parameters":[{"in":"query","name":"id","description":"The ID for the Pet","required":false,"type":"integer"},{"in":"path","name":"id","description":"Scope response to id","required":true,"type":"string"}],"responses":{"default":{"description":"successful operation"}}}}},"definitions":{}}
1
+ {"swagger":"2.0","info":{"version":"0.1","title":"","description":"","termsOfService":"","contact":{"name":""},"license":{"name":""}},"host":"localhost:3000","basePath":"/","schemes":["https","http"],"consumes":["application/xml","application/json"],"produces":["application/xml","application/json"],"tags":[{"name":"admin/pets","description":"This document describes the API for interacting with Pet resources"},{"name":"pets","description":"This document describes the API for interacting with Pet resources"}],"paths":{"/admin/pets":{"get":{"tags":["admin/pets"],"operationId":"index","summary":"return a list of Pets","description":"","produces":["application/xml","application/json"],"parameters":[],"responses":{"default":{"description":"successful operation"}}}},"/pets":{"get":{"tags":["pets"],"operationId":"index","summary":"return a list of Pets","description":"","produces":["application/xml","application/json"],"parameters":[],"responses":{"default":{"description":"successful operation"}}}},"/pets/{id}":{"get":{"tags":["pets"],"operationId":"show","summary":"return a Pet","description":"","produces":["application/xml","application/json"],"parameters":[{"name":"id","in":"path","required":true,"type":"string","description":"Scope response to id"},{"name":"id","in":"query","required":false,"type":"integer","format":"int32","description":"The ID for the Pet"}],"responses":{"default":{"description":"successful operation"}}}}},"definitions":{}}
@@ -3,7 +3,6 @@ require 'spec_helper'
3
3
  require 'json-schema'
4
4
 
5
5
  describe Swaggard, '.get_doc' do
6
-
7
6
  let(:controller_path) { File.expand_path('../../fixtures/dummy/app/controllers/**/*.rb', __FILE__) }
8
7
  let(:api_json) { File.read(File.expand_path('../../fixtures/api.json', __FILE__)) }
9
8
 
@@ -19,5 +18,4 @@ describe Swaggard, '.get_doc' do
19
18
 
20
19
  expect(swagger_json).to eq(api_json)
21
20
  end
22
-
23
21
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: swaggard
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.4
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adrian Gomez
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-07-18 00:00:00.000000000 Z
11
+ date: 2019-07-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -166,7 +166,7 @@ files:
166
166
  - lib/swaggard/api_definition.rb
167
167
  - lib/swaggard/configuration.rb
168
168
  - lib/swaggard/engine.rb
169
- - lib/swaggard/parsers/controllers.rb
169
+ - lib/swaggard/parsers/controller.rb
170
170
  - lib/swaggard/parsers/models.rb
171
171
  - lib/swaggard/parsers/routes.rb
172
172
  - lib/swaggard/swagger/definition.rb
@@ -180,6 +180,7 @@ files:
180
180
  - lib/swaggard/swagger/path.rb
181
181
  - lib/swaggard/swagger/property.rb
182
182
  - lib/swaggard/swagger/response.rb
183
+ - lib/swaggard/swagger/response_header.rb
183
184
  - lib/swaggard/swagger/tag.rb
184
185
  - lib/swaggard/swagger/type.rb
185
186
  - lib/swaggard/version.rb
@@ -214,20 +215,19 @@ required_rubygems_version: !ruby/object:Gem::Requirement
214
215
  - !ruby/object:Gem::Version
215
216
  version: '0'
216
217
  requirements: []
217
- rubyforge_project:
218
- rubygems_version: 2.5.1
218
+ rubygems_version: 3.0.3
219
219
  signing_key:
220
220
  specification_version: 4
221
221
  summary: 'Swaggard: Swagger Rails REST API doc using yard YARD'
222
222
  test_files:
223
- - spec/fixtures/api.json
224
- - spec/fixtures/dummy/app/controllers/admin/pets_controller.rb
225
- - spec/fixtures/dummy/app/controllers/application_controller.rb
223
+ - spec/spec_helper.rb
224
+ - spec/integration/swaggard_spec.rb
225
+ - spec/fixtures/swagger_schema.json
226
226
  - spec/fixtures/dummy/app/controllers/pets_controller.rb
227
- - spec/fixtures/dummy/config/application.rb
228
- - spec/fixtures/dummy/config/environments/development.rb
227
+ - spec/fixtures/dummy/app/controllers/application_controller.rb
228
+ - spec/fixtures/dummy/app/controllers/admin/pets_controller.rb
229
229
  - spec/fixtures/dummy/config/routes.rb
230
+ - spec/fixtures/dummy/config/environments/development.rb
231
+ - spec/fixtures/dummy/config/application.rb
230
232
  - spec/fixtures/dummy/log/development.log
231
- - spec/fixtures/swagger_schema.json
232
- - spec/integration/swaggard_spec.rb
233
- - spec/spec_helper.rb
233
+ - spec/fixtures/api.json