swaggard 0.5.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 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