swagger-docs 0.1.1 → 0.1.2

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
2
  SHA1:
3
- metadata.gz: f824965e424cbfdcd8551dea96dbabe88e7368fd
4
- data.tar.gz: 2a02f6c05ea8aa87659b022f4807bf2a9874639f
3
+ metadata.gz: 9940ad082299dc2d0d6cb34a3e7c1336a37e0c8b
4
+ data.tar.gz: 9f75192abe6f35735aea5b4948482211177b5cf0
5
5
  SHA512:
6
- metadata.gz: 81971dd258cb50fae6a2a0e8c6837deff20da9f9067e6a4a433b6c04e4a3351fa035d59871fb263f96bae53db2164b9b053013d487e427129619ad3956c7b8bd
7
- data.tar.gz: 817550eddc45f754818bebdfa134fc6bff885a6fa6796116bf2f7da4530bba0458f2cc076e569d16dc602129177c14f7969bb8ca3b7a33eb9f0f8a4a80a950e5
6
+ metadata.gz: 9d1a4e4f8a4c83ed76b547414ff6c1ac1887d3570bcb43b90b665d7e8ca9c97ee7f5de6cdd097d5c790bac0a8fdf2dbd53b04c72ae892e85c415b0757bdd444b
7
+ data.tar.gz: abba4ab15746a0ec3e1b8c8b2bbe78f35c62438289ed53641b1b682c5204eb971f7b6c67d67103a29093c5eb5eb49025a6194012b6564f07cdc4229c0e44b875
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## 0.1.2
2
+ - Add suport for Swagger models
3
+ - Use ActionControlller::Base instead of ApplicationController. fixes #27
4
+ - Status codes for response
5
+ - Path generation fixes #26 @stevschmid
6
+ - Ignore path filtering when no params are set
7
+ - Add param_list helper for generating enums/lists
8
+ - Improve structure of generator class - break up large methods
9
+ - Fix the destination path of the resource files #30
10
+
1
11
  ## 0.1.1
2
12
  - Add support for Rails engines (@fotinakis)
3
13
  - Filter out path parameters if the parameter is not in the path (@stevschmid)
data/README.md CHANGED
@@ -42,11 +42,11 @@ Swagger::Docs::Config.register_apis({
42
42
  # the extension used for the API
43
43
  :api_extension_type => :json,
44
44
  # the output location where your .json files are written to
45
- :api_file_path => "public/api/v1/",
46
- # the URL base path to your API
47
- :base_path => "http://api.somedomain.com",
45
+ :api_file_path => "public/api/v1/",
46
+ # the URL base path to your API
47
+ :base_path => "http://api.somedomain.com",
48
48
  # if you want to delete all .json files at each generation
49
- :clean_directory => false
49
+ :clean_directory => false
50
50
  }
51
51
  })
52
52
  ```
@@ -109,8 +109,9 @@ class Api::V1::UsersController < ApplicationController
109
109
  swagger_api :index do
110
110
  summary "Fetches all User items"
111
111
  param :query, :page, :integer, :optional, "Page number"
112
+ param :path, :nested_id, :integer, :optional, "Team Id"
112
113
  response :unauthorized
113
- response :not_acceptable
114
+ response :not_acceptable, "The request you made is not acceptable"
114
115
  response :requested_range_not_satisfiable
115
116
  end
116
117
 
@@ -127,6 +128,7 @@ class Api::V1::UsersController < ApplicationController
127
128
  param :form, :first_name, :string, :required, "First name"
128
129
  param :form, :last_name, :string, :required, "Last name"
129
130
  param :form, :email, :string, :required, "Email address"
131
+ param_list :form, :role, :string, :required, "Role", [ "admin", "superadmin", "user" ]
130
132
  response :unauthorized
131
133
  response :not_acceptable
132
134
  end
@@ -137,6 +139,7 @@ class Api::V1::UsersController < ApplicationController
137
139
  param :form, :first_name, :string, :optional, "First name"
138
140
  param :form, :last_name, :string, :optional, "Last name"
139
141
  param :form, :email, :string, :optional, "Email address"
142
+ param :form, :tag, :Tag, :required, "Tag object"
140
143
  response :unauthorized
141
144
  response :not_found
142
145
  response :not_acceptable
@@ -149,9 +152,51 @@ class Api::V1::UsersController < ApplicationController
149
152
  response :not_found
150
153
  end
151
154
 
155
+ # Support for Swagger complex types:
156
+ # https://github.com/wordnik/swagger-core/wiki/Datatypes#wiki-complex-types
157
+ swagger_model :Tag do
158
+ description "A Tag object."
159
+ property :id, :integer, :required, "User Id"
160
+ property :name, :string, :optional, "Name"
161
+ end
162
+
152
163
  end
153
164
  ```
154
165
 
166
+ ### DSL Methods
167
+
168
+ <table>
169
+ <thead>
170
+ <tr>
171
+ <th>Method</th>
172
+ <th>Description</th>
173
+ </tr>
174
+ </thead>
175
+ <tbody>
176
+
177
+ <tr>
178
+ <td>summary</td>
179
+ <td>The summary of the API</td>
180
+ </tr>
181
+
182
+ <tr>
183
+ <td>param</td>
184
+ <td>Standard API Parameter</td>
185
+ </tr>
186
+
187
+ <tr>
188
+ <td>param_list</td>
189
+ <td>Standard API Enum/List parameter.</td>
190
+ </tr>
191
+
192
+ <tr>
193
+ <td>response</td>
194
+ <td>Takes a symbol or status code and passes it to `Rack::Utils.status_code`. The current list of status codes can be seen here: https://github.com/rack/rack/blob/master/lib/rack/utils.rb. An optional message can be added.</td>
195
+ </tr>
196
+
197
+ </tbody>
198
+ </table>
199
+
155
200
  ### Run rake task to generate docs
156
201
 
157
202
  ```
@@ -160,7 +205,7 @@ rake swagger:docs
160
205
 
161
206
  Swagger-ui JSON files should now be present in your api_file_path (e.g. ./public/api/v1)
162
207
 
163
- ### Sample
208
+ ### Sample
164
209
 
165
210
  A sample Rails application where you can run the above rake command and view the output in swagger-ui can be found here:
166
211
 
@@ -173,8 +218,8 @@ https://github.com/richhollis/swagger-docs-sample
173
218
 
174
219
  #### Inheriting from a custom Api controller
175
220
 
176
- By default swagger-docs is applied to controllers inheriting from ApplicationController.
177
- If this is not the case for your application, use this snippet in your initializer
221
+ By default swagger-docs is applied to controllers inheriting from ApplicationController.
222
+ If this is not the case for your application, use this snippet in your initializer
178
223
  _before_ calling Swagger::Docs::Config#register_apis(...).
179
224
 
180
225
  ```ruby
@@ -194,7 +239,6 @@ class Swagger::Docs::Config
194
239
  end
195
240
  ```
196
241
 
197
- =======
198
242
  #### Transforming the `path` variable
199
243
 
200
244
  Swagger allows a distinction between the API documentation server and the hosted API
@@ -253,7 +297,7 @@ users.json output:
253
297
  {
254
298
  "apiVersion": "1.0",
255
299
  "swaggerVersion": "1.2",
256
- "basePath": "/api/v1",
300
+ "basePath": "http://api.somedomain.com/api/v1/",
257
301
  "resourcePath": "/users",
258
302
  "apis": [
259
303
  {
@@ -277,7 +321,47 @@ users.json output:
277
321
  },
278
322
  {
279
323
  "code": 406,
280
- "message": "Not Acceptable"
324
+ "message": "The request you made is not acceptable"
325
+ },
326
+ {
327
+ "code": 416,
328
+ "message": "Requested Range Not Satisfiable"
329
+ }
330
+ ],
331
+ "method": "get",
332
+ "nickname": "Api::V1::Users#index"
333
+ }
334
+ ]
335
+ },
336
+ {
337
+ "path": "nested/{nested_id}/sample",
338
+ "operations": [
339
+ {
340
+ "summary": "Fetches all User items",
341
+ "parameters": [
342
+ {
343
+ "paramType": "query",
344
+ "name": "page",
345
+ "type": "integer",
346
+ "description": "Page number",
347
+ "required": false
348
+ },
349
+ {
350
+ "paramType": "path",
351
+ "name": "nested_id",
352
+ "type": "integer",
353
+ "description": "Team Id",
354
+ "required": false
355
+ }
356
+ ],
357
+ "responseMessages": [
358
+ {
359
+ "code": 401,
360
+ "message": "Unauthorized"
361
+ },
362
+ {
363
+ "code": 406,
364
+ "message": "The request you made is not acceptable"
281
365
  },
282
366
  {
283
367
  "code": 416,
@@ -398,6 +482,13 @@ users.json output:
398
482
  "type": "string",
399
483
  "description": "Email address",
400
484
  "required": false
485
+ },
486
+ {
487
+ "paramType": "form",
488
+ "name": "tag",
489
+ "type": "Tag",
490
+ "description": "Tag object",
491
+ "required": true
401
492
  }
402
493
  ],
403
494
  "responseMessages": [
@@ -448,7 +539,27 @@ users.json output:
448
539
  }
449
540
  ]
450
541
  }
451
- ]
542
+ ],
543
+ "models": {
544
+ "Tag": {
545
+ "id": "Tag",
546
+ "required": [
547
+ "id"
548
+ ],
549
+ "properties": {
550
+ "id": {
551
+ "type": "integer",
552
+ "description": "User Id"
553
+ },
554
+ "name": {
555
+ "type": "string",
556
+ "description": "Name",
557
+ "foo": "test"
558
+ }
559
+ },
560
+ "description": "A Tag object."
561
+ }
562
+ }
452
563
  }
453
564
  ```
454
565
 
@@ -2,7 +2,7 @@ module Swagger
2
2
  module Docs
3
3
  class Config
4
4
  class << self
5
- def base_api_controller; ApplicationController end
5
+ def base_api_controller; ActionController::Base end
6
6
  def base_application; Rails.application end
7
7
  def register_apis(versions)
8
8
  base_api_controller.send(:include, ImpotentMethods)
@@ -2,10 +2,10 @@ module Swagger
2
2
  module Docs
3
3
  class SwaggerDSL
4
4
  # http://stackoverflow.com/questions/5851127/change-the-context-binding-inside-a-block-in-ruby/5851325#5851325
5
- def self.call(action, caller, &blk)
6
- # Create a new CommandDSL instance, and instance_eval the block to it
5
+ def self.call(action, caller, &block)
6
+ # Create a new SwaggerDSL instance, and instance_eval the block to it
7
7
  instance = new
8
- instance.instance_eval(&blk)
8
+ instance.instance_eval(&block)
9
9
  # Now return all of the set instance variables as a Hash
10
10
  instance.instance_variables.inject({}) { |result_hash, instance_variable|
11
11
  result_hash[instance_variable] = instance.instance_variable_get(instance_variable)
@@ -38,6 +38,12 @@ module Swagger
38
38
  :description => description, :required => required == :required ? true : false}.merge(hash)
39
39
  end
40
40
 
41
+ # helper method to generate enums
42
+ def param_list(param_type, name, type, required, description = nil, allowed_values = [], hash = {})
43
+ hash.merge!({allowable_values: {value_type: "LIST", values: allowed_values}})
44
+ param(param_type, name, type, required, description = nil, hash)
45
+ end
46
+
41
47
  def response_messages
42
48
  @response_messages ||= []
43
49
  end
@@ -52,5 +58,43 @@ module Swagger
52
58
  response_messages.sort_by!{|i| i[:code]}
53
59
  end
54
60
  end
61
+
62
+ class SwaggerModelDSL
63
+ attr_accessor :id
64
+
65
+ # http://stackoverflow.com/questions/5851127/change-the-context-binding-inside-a-block-in-ruby/5851325#5851325
66
+ def self.call(model_name, caller, &block)
67
+ # Create a new SwaggerModelDSL instance, and instance_eval the block to it
68
+ instance = new
69
+ instance.instance_eval(&block)
70
+ instance.id = model_name
71
+ # Now return all of the set instance variables as a Hash
72
+ instance.instance_variables.inject({}) { |result_hash, instance_var_name|
73
+ key = instance_var_name[1..-1].to_sym # Strip prefixed @ sign.
74
+ result_hash[key] = instance.instance_variable_get(instance_var_name)
75
+ result_hash # Gotta have the block return the result_hash
76
+ }
77
+ end
78
+
79
+ def properties
80
+ @properties ||= {}
81
+ end
82
+
83
+ def required
84
+ @required ||= []
85
+ end
86
+
87
+ def description(description)
88
+ @description = description
89
+ end
90
+
91
+ def property(name, type, required, description = nil, hash={})
92
+ properties[name] = {
93
+ type: type,
94
+ description: description,
95
+ }.merge!(hash)
96
+ self.required << name if (required == :required ? true : false)
97
+ end
98
+ end
55
99
  end
56
100
  end
@@ -12,6 +12,63 @@ module Swagger
12
12
 
13
13
  class << self
14
14
 
15
+ def set_real_methods
16
+ Config.base_api_controller.send(:include, Methods) # replace impotent methods with live ones
17
+ end
18
+
19
+ def write_docs(apis = nil)
20
+ apis ||= Config.registered_apis
21
+ results = {}
22
+ set_real_methods
23
+ unless apis.empty?
24
+ apis.each do |api_version,config|
25
+ config.reverse_merge!(DEFAULT_CONFIG)
26
+ results[api_version] = write_doc(api_version, config)
27
+ end
28
+ else
29
+ results[DEFAULT_VER] = write_doc(DEFAULT_VER, DEFAULT_CONFIG)
30
+ end
31
+ results
32
+ end
33
+
34
+ def write_doc(api_version, config)
35
+ settings = get_settings(api_version, config)
36
+
37
+ create_output_paths(settings[:api_file_path])
38
+ clean_output_paths(settings[:api_file_path]) if config[:clean_directory] || false
39
+
40
+ root = { :api_version => api_version, :swagger_version => "1.2", :base_path => settings[:base_path] + "/", :apis => []}
41
+ results = {:processed => [], :skipped => []}
42
+
43
+ get_route_paths(settings[:controller_base_path]).each do |path|
44
+ ret = process_path(path, root, config, settings)
45
+ results[ret[:action]] << ret
46
+ if ret[:action] == :processed
47
+ create_resource_file(ret[:path], ret[:apis], ret[:models], settings, root, config)
48
+ debased_path = get_debased_path(ret[:path], settings[:controller_base_path])
49
+ resource_api = {
50
+ path: "#{Config.transform_path(trim_leading_slash(debased_path))}.{format}",
51
+ description: ret[:klass].swagger_config[:description]
52
+ }
53
+ root[:apis] << resource_api
54
+ end
55
+ end
56
+
57
+ camelize_keys_deep!(root)
58
+ write_to_file("#{settings[:api_file_path]}/api-docs.json", root, config)
59
+ results
60
+ end
61
+
62
+ private
63
+
64
+ def transform_spec_to_api_path(spec, controller_base_path, extension)
65
+ api_path = spec.to_s.dup
66
+ api_path.gsub!('(.:format)', extension ? ".#{extension}" : '')
67
+ api_path.gsub!(/:(\w+)/, '{\1}')
68
+ api_path.gsub!(controller_base_path, '')
69
+ trim_slashes(api_path)
70
+ end
71
+
15
72
  def camelize_keys_deep!(h)
16
73
  h.keys.each do |k|
17
74
  ks = k.to_s.camelize(:lower)
@@ -26,107 +83,108 @@ module Swagger
26
83
  end
27
84
  end
28
85
 
29
- def get_api_path(spec, extension)
30
- extension = ".#{extension}" if extension
31
- path_api = trim_leading_slash(spec.to_s.gsub("(.:format)", extension.to_s))
32
- parts_new = []
33
- path_api.split("/").each do |path_part|
34
- part = path_part
35
- if part[0] == ":"
36
- part[0] = "{"
37
- part << "}"
38
- end
39
- parts_new << part
40
- end
41
- path_api = parts_new*"/"
42
- end
43
-
44
86
  def trim_leading_slash(str)
45
87
  return str if !str
46
- return str unless str[0] == '/'
47
- str[1..-1]
88
+ str.gsub(/\A\/+/, '')
48
89
  end
49
90
 
50
91
  def trim_trailing_slash(str)
51
92
  return str if !str
52
- return str unless str[-1] == '/'
53
- str[0..-2]
93
+ str.gsub(/\/+\z/, '')
54
94
  end
55
95
 
56
96
  def trim_slashes(str)
57
97
  trim_leading_slash(trim_trailing_slash(str))
58
98
  end
59
99
 
60
- def set_real_methods
61
- Config.base_api_controller.send(:include, Methods) # replace impotent methods with live ones
100
+ def get_debased_path(path, controller_base_path)
101
+ path.gsub("#{controller_base_path}", "")
62
102
  end
63
103
 
64
- def write_docs(apis = nil)
65
- apis ||= Config.registered_apis
66
- results = {}
67
- set_real_methods
68
- unless apis.empty?
69
- apis.each do |api_version,config|
70
- config.reverse_merge!(DEFAULT_CONFIG)
71
- results[api_version] = write_doc(api_version, config)
72
- end
73
- else
74
- results[DEFAULT_VER] = write_doc(DEFAULT_VER, DEFAULT_CONFIG)
104
+ def process_path(path, root, config, settings)
105
+ return {action: empty} if path.empty?
106
+ klass = "#{path.to_s.camelize}Controller".constantize
107
+ return {action: :skipped, path: path} if !klass.methods.include?(:swagger_config) or !klass.swagger_config[:controller]
108
+ apis, models = [], {}
109
+ Config.base_application.routes.routes.select{|i| i.defaults[:controller] == path}.each do |route|
110
+ ret = get_route_path_apis(path, route, klass, settings, config)
111
+ apis = apis + ret[:apis]
112
+ models.merge!(ret[:models])
75
113
  end
76
- results
114
+ {action: :processed, path: path, apis: apis, models: models, klass: klass}
77
115
  end
78
116
 
79
- def write_doc(api_version, config)
80
- base_path = trim_trailing_slash(config[:base_path] || "")
81
- controller_base_path = trim_leading_slash(config[:controller_base_path] || "")
82
- api_file_path = config[:api_file_path]
83
- clean_directory = config[:clean_directory] || false
84
- results = {:processed => [], :skipped => []}
117
+ def create_resource_file(path, apis, models, settings, root, config)
118
+ debased_path = get_debased_path(path, settings[:controller_base_path])
119
+ demod = "#{debased_path.to_s.camelize}".demodulize.camelize.underscore
120
+ resource_path = trim_leading_slash(debased_path.to_s.underscore)
121
+ resource = root.merge({:resource_path => "#{demod}", :apis => apis})
122
+ camelize_keys_deep!(resource)
123
+ # Add the already-normalized models to the resource.
124
+ resource = resource.merge({:models => models}) if models.present?
125
+ # write controller resource file
126
+ write_to_file(File.join(settings[:api_file_path], "#{resource_path}.json"), resource, config)
127
+ end
85
128
 
86
- # create output paths
87
- FileUtils.mkdir_p(api_file_path) # recursively create out output path
88
- Dir.foreach(api_file_path) {|f| fn = File.join(api_file_path, f); File.delete(fn) if !File.directory?(fn) and File.extname(fn) == '.json'} if clean_directory # clean output path
129
+ def get_route_path_apis(path, route, klass, settings, config)
130
+ models, apis = {}, []
131
+ action = route.defaults[:action]
132
+ verb = route.verb.source.to_s.delete('$'+'^').downcase.to_sym
133
+ return apis if !operations = klass.swagger_actions[action.to_sym]
134
+ operations = Hash[operations.map {|k, v| [k.to_s.gsub("@","").to_sym, v.respond_to?(:deep_dup) ? v.deep_dup : v.dup] }] # rename :@instance hash keys
135
+ operations[:method] = verb
136
+ operations[:nickname] = "#{path.camelize}##{action}"
137
+
138
+ api_path = transform_spec_to_api_path(route.path.spec, settings[:controller_base_path], config[:api_extension_type])
139
+ operations[:parameters] = filter_path_params(api_path, operations[:parameters]) if operations[:parameters]
140
+
141
+ apis << {:path => api_path, :operations => [operations]}
142
+ models = get_klass_models(klass)
143
+
144
+ {apis: apis, models: models}
145
+ end
146
+
147
+ def get_klass_models(klass)
148
+ models = {}
149
+ # Add any declared models to the root of the resource.
150
+ klass.swagger_models.each do |model_name, model|
151
+ formatted_model = {
152
+ id: model[:id],
153
+ required: model[:required],
154
+ properties: model[:properties],
155
+ }
156
+ formatted_model[:description] = model[:description] if model[:description]
157
+ models[model[:id]] = formatted_model
158
+ end
159
+ models
160
+ end
89
161
 
162
+ def get_settings(api_version, config)
163
+ base_path = trim_trailing_slash(config[:base_path] || "")
164
+ controller_base_path = trim_leading_slash(config[:controller_base_path] || "")
90
165
  base_path += "/#{controller_base_path}" unless controller_base_path.empty?
91
- header = { :api_version => api_version, :swagger_version => "1.2", :base_path => base_path + "/"}
92
- resources = header.merge({:apis => []})
166
+ api_file_path = config[:api_file_path]
167
+ settings = {
168
+ base_path: base_path,
169
+ controller_base_path: controller_base_path,
170
+ api_file_path: api_file_path
171
+ }.freeze
172
+ end
93
173
 
174
+ def get_route_paths(controller_base_path)
94
175
  paths = Config.base_application.routes.routes.map{|i| "#{i.defaults[:controller]}" }
95
- paths = paths.uniq.select{|i| i.start_with?(controller_base_path)}
96
- paths.each do |path|
97
- next if path.empty?
98
- klass = "#{path.to_s.camelize}Controller".constantize
99
- if !klass.methods.include?(:swagger_config) or !klass.swagger_config[:controller]
100
- results[:skipped] << path
101
- next
102
- end
103
- apis = []
104
- debased_path = path.gsub("#{controller_base_path}", "")
105
- Config.base_application.routes.routes.select{|i| i.defaults[:controller] == path}.each do |route|
106
- action = route.defaults[:action]
107
- verb = route.verb.source.to_s.delete('$'+'^').downcase.to_sym
108
- next if !operations = klass.swagger_actions[action.to_sym]
109
- operations = Hash[operations.map {|k, v| [k.to_s.gsub("@","").to_sym, v.respond_to?(:deep_dup) ? v.deep_dup : v.dup] }] # rename :@instance hash keys
110
- operations[:method] = verb
111
- operations[:nickname] = "#{path.camelize}##{action}"
112
- api_path = trim_slashes(get_api_path(trim_leading_slash(route.path.spec.to_s), config[:api_extension_type]).gsub("#{controller_base_path}",""))
113
- operations[:parameters] = filter_path_params(api_path, operations[:parameters])
114
- apis << {:path => api_path, :operations => [operations]}
115
- end
116
- demod = "#{debased_path.to_s.camelize}".demodulize.camelize.underscore
117
- resource = header.merge({:resource_path => "#{demod}", :apis => apis})
118
- camelize_keys_deep!(resource)
119
- # write controller resource file
120
- write_to_file "#{api_file_path}/#{demod}.json", resource, config
121
- # append resource to resources array (for writing out at end)
122
- resources[:apis] << {path: "#{Config.transform_path(trim_leading_slash(debased_path))}.{format}", description: klass.swagger_config[:description]}
123
- results[:processed] << path
124
- end
125
- # write master resource file
126
- camelize_keys_deep!(resources)
176
+ paths.uniq.select{|i| i.start_with?(controller_base_path)}
177
+ end
127
178
 
128
- write_to_file "#{api_file_path}/api-docs.json", resources, config
129
- results
179
+ def create_output_paths(api_file_path)
180
+ FileUtils.mkdir_p(api_file_path) # recursively create out output path
181
+ end
182
+
183
+ def clean_output_paths(api_file_path)
184
+ Dir.foreach(api_file_path) do |f|
185
+ fn = File.join(api_file_path, f)
186
+ File.delete(fn) if !File.directory?(fn) and File.extname(fn) == '.json'
187
+ end
130
188
  end
131
189
 
132
190
  def write_to_file(path, structure, config={})
@@ -134,11 +192,10 @@ module Swagger
134
192
  when :pretty; JSON.pretty_generate structure
135
193
  else; structure.to_json
136
194
  end
195
+ FileUtils.mkdir_p File.dirname(path)
137
196
  File.open(path, 'w') { |file| file.write content }
138
197
  end
139
198
 
140
- private
141
-
142
199
  def filter_path_params(path, params)
143
200
  params.reject do |param|
144
201
  param_as_variable = "{#{param[:name]}}"
@@ -12,6 +12,9 @@ module Swagger
12
12
  def swagger_api(action, &block)
13
13
  end
14
14
 
15
+ def swagger_model(model_name, &block)
16
+ end
17
+
15
18
  def swagger_controller(controller, description)
16
19
  end
17
20
  end
@@ -11,14 +11,14 @@ module Swagger
11
11
  swagger_config[:description] = description
12
12
  end
13
13
 
14
- def swagger_model(model)
15
- swagger_config[:model] = model
16
- end
17
-
18
14
  def swagger_actions
19
15
  @swagger_dsl
20
16
  end
21
17
 
18
+ def swagger_models
19
+ @swagger_model_dsls ||= {}
20
+ end
21
+
22
22
  def swagger_config
23
23
  @swagger_config ||= {}
24
24
  end
@@ -27,12 +27,16 @@ module Swagger
27
27
 
28
28
  def swagger_api(action, &block)
29
29
  @swagger_dsl ||= {}
30
- controller_action = "#{name}##{action} #{self.class}"
31
30
  return if @swagger_dsl[action]
32
- route = Swagger::Docs::Config.base_application.routes.routes.select{|i| "#{i.defaults[:controller].to_s.camelize}Controller##{i.defaults[:action]}" == controller_action }.first
33
31
  dsl = SwaggerDSL.call(action, self, &block)
34
32
  @swagger_dsl[action] = dsl
35
33
  end
34
+
35
+ def swagger_model(model_name, &block)
36
+ @swagger_model_dsls ||= {}
37
+ model_dsl = SwaggerModelDSL.call(model_name, self, &block)
38
+ @swagger_model_dsls[model_name] = model_dsl
39
+ end
36
40
  end
37
41
  end
38
42
  end
@@ -1,5 +1,5 @@
1
1
  module Swagger
2
2
  module Docs
3
- VERSION = "0.1.1"
3
+ VERSION = "0.1.2"
4
4
  end
5
5
  end
@@ -27,6 +27,7 @@ module Api
27
27
  param :form, :first_name, :string, :required, "First name"
28
28
  param :form, :last_name, :string, :required, "Last name"
29
29
  param :form, :email, :string, :required, "Email address"
30
+ param_list :form, :role, :string, :required, "Role", [ "admin", "superadmin", "user" ]
30
31
  response :unauthorized
31
32
  response :not_acceptable
32
33
  end
@@ -37,6 +38,7 @@ module Api
37
38
  param :form, :first_name, :string, :optional, "First name"
38
39
  param :form, :last_name, :string, :optional, "Last name"
39
40
  param :form, :email, :string, :optional, "Email address"
41
+ param :form, :tag, :Tag, :required, "Tag object"
40
42
  response :unauthorized
41
43
  response :not_found
42
44
  response :not_acceptable
@@ -49,6 +51,18 @@ module Api
49
51
  response :not_found
50
52
  end
51
53
 
54
+ # a method that intentionally has no parameters
55
+ swagger_api :new do
56
+ summary "Builds a new User item"
57
+ end
58
+
59
+ # Support for Swagger complex types:
60
+ # https://github.com/wordnik/swagger-core/wiki/Datatypes#wiki-complex-types
61
+ swagger_model :Tag do
62
+ description "A Tag object."
63
+ property :id, :integer, :required, "User Id"
64
+ property :name, :string, :optional, "Name", foo: "test"
65
+ end
52
66
  end
53
67
  end
54
68
  end
@@ -5,19 +5,9 @@ describe Swagger::Docs::Generator do
5
5
  require "fixtures/controllers/application_controller"
6
6
  require "fixtures/controllers/ignored_controller"
7
7
 
8
- def generate(config)
9
- Swagger::Docs::Generator::write_docs(config)
10
- end
11
-
12
- def stub_route(verb, action, controller, spec)
13
- double("route", :verb => double("verb", :source => verb),
14
- :defaults => {:action => action, :controller => controller},
15
- :path => double("path", :spec => spec)
16
- )
17
- end
18
-
19
8
  before(:each) do
20
- FileUtils.rm_rf(TMP_DIR)
9
+ FileUtils.rm_rf(tmp_dir)
10
+ stub_const('ActionController::Base', ApplicationController)
21
11
  end
22
12
 
23
13
  let(:routes) {[
@@ -27,13 +17,18 @@ describe Swagger::Docs::Generator do
27
17
  stub_route("^POST$", "create", "api/v1/sample", "/api/v1/sample(.:format)"),
28
18
  stub_route("^GET$", "show", "api/v1/sample", "/api/v1/sample/:id(.:format)"),
29
19
  stub_route("^PUT$", "update", "api/v1/sample", "/api/v1/sample/:id(.:format)"),
30
- stub_route("^DELETE$", "destroy", "api/v1/sample", "/api/v1/sample/:id(.:format)")
20
+ stub_route("^DELETE$", "destroy", "api/v1/sample", "/api/v1/sample/:id(.:format)"),
21
+ stub_route("^GET$", "new", "api/v1/sample", "/api/v1/sample/new(.:format)") # no parameters for this method
31
22
  ]}
32
23
 
24
+ let(:tmp_dir) { Pathname.new('/tmp/swagger-docs/') }
25
+ let(:file_resources) { tmp_dir + 'api-docs.json' }
26
+ let(:file_resource) { tmp_dir + 'api/v1/sample.json' }
27
+
33
28
  context "without controller base path" do
34
- let(:config) {
29
+ let(:config) {
35
30
  {
36
- DEFAULT_VER => {:api_file_path => "#{TMP_DIR}api/v1/", :base_path => "http://api.no.where"}
31
+ DEFAULT_VER => {:api_file_path => "#{tmp_dir}", :base_path => "http://api.no.where"}
37
32
  }
38
33
  }
39
34
  before(:each) do
@@ -43,7 +38,7 @@ describe Swagger::Docs::Generator do
43
38
  generate(config)
44
39
  end
45
40
  context "resources files" do
46
- let(:resources) { FILE_RESOURCES.read }
41
+ let(:resources) { file_resources.read }
47
42
  let(:response) { JSON.parse(resources) }
48
43
  it "writes basePath correctly" do
49
44
  expect(response["basePath"]).to eq "http://api.no.where/"
@@ -56,7 +51,7 @@ describe Swagger::Docs::Generator do
56
51
  end
57
52
  end
58
53
  context "resource file" do
59
- let(:resource) { FILE_RESOURCE.read }
54
+ let(:resource) { file_resource.read }
60
55
  let(:response) { JSON.parse(resource) }
61
56
  let(:first) { response["apis"].first }
62
57
  let(:operations) { first["operations"] }
@@ -68,7 +63,7 @@ describe Swagger::Docs::Generator do
68
63
  expect(response["resourcePath"]).to eq "sample"
69
64
  end
70
65
  it "writes out expected api count" do
71
- expect(response["apis"].count).to eq 6
66
+ expect(response["apis"].count).to eq 7
72
67
  end
73
68
  context "first api" do
74
69
  #"apis":[{"path":" /sample","operations":[{"summary":"Fetches all User items"
@@ -82,9 +77,10 @@ describe Swagger::Docs::Generator do
82
77
 
83
78
  context "with controller base path" do
84
79
  let(:config) { Swagger::Docs::Config.register_apis({
85
- DEFAULT_VER => {:controller_base_path => "api/v1", :api_file_path => "#{TMP_DIR}api/v1/", :base_path => "http://api.no.where"}
80
+ DEFAULT_VER => {:controller_base_path => "api/v1", :api_file_path => "#{tmp_dir}", :base_path => "http://api.no.where"}
86
81
  })}
87
- before(:each) do
82
+ let(:file_resource) { tmp_dir + 'sample.json' }
83
+ before(:each) do
88
84
  Rails.stub_chain(:application, :routes, :routes).and_return(routes)
89
85
  Swagger::Docs::Generator.set_real_methods
90
86
  require "fixtures/controllers/sample_controller"
@@ -92,16 +88,16 @@ describe Swagger::Docs::Generator do
92
88
 
93
89
  context "test suite initialization" do
94
90
  it "the resources file does not exist" do
95
- expect(FILE_RESOURCES).to_not exist
91
+ expect(file_resource).to_not exist
96
92
  end
97
93
  it "the resource file does not exist" do
98
- expect(FILE_RESOURCE).to_not exist
94
+ expect(file_resource).to_not exist
99
95
  end
100
96
  end
101
97
 
102
98
  describe "#write_docs" do
103
99
  context "no apis registered" do
104
- before(:each) do
100
+ before(:each) do
105
101
  Swagger::Docs::Config.register_apis({})
106
102
  end
107
103
  it "generates using default config" do
@@ -113,7 +109,7 @@ describe Swagger::Docs::Generator do
113
109
  generate(config)
114
110
  end
115
111
  it "cleans json files in directory when set" do
116
- file_to_delete = TMP_DIR+"api/v1/delete_me.json"
112
+ file_to_delete = Pathname.new(File.join(config['1.0'][:api_file_path], 'delete_me.json'))
117
113
  File.open(file_to_delete, 'w') {|f| f.write("{}") }
118
114
  expect(file_to_delete).to exist
119
115
  config[DEFAULT_VER][:clean_directory] = true
@@ -121,17 +117,17 @@ describe Swagger::Docs::Generator do
121
117
  expect(file_to_delete).to_not exist
122
118
  end
123
119
  it "keeps non json files in directory when cleaning" do
124
- file_to_keep = TMP_DIR+"api/v1/keep_me"
120
+ file_to_keep = Pathname.new(File.join(config['1.0'][:api_file_path], 'keep_me'))
125
121
  File.open(file_to_keep, 'w') {|f| f.write("{}") }
126
122
  config[DEFAULT_VER][:clean_directory] = true
127
123
  generate(config)
128
124
  expect(file_to_keep).to exist
129
125
  end
130
126
  it "writes the resources file" do
131
- expect(FILE_RESOURCES).to exist
127
+ expect(file_resources).to exist
132
128
  end
133
129
  it "writes the resource file" do
134
- expect(FILE_RESOURCE).to exist
130
+ expect(file_resource).to exist
135
131
  end
136
132
  it "returns results hash" do
137
133
  results = generate(config)
@@ -141,11 +137,11 @@ describe Swagger::Docs::Generator do
141
137
  it "writes pretty json files when set" do
142
138
  config[DEFAULT_VER][:formatting] = :pretty
143
139
  generate(config)
144
- resources = File.read FILE_RESOURCES
140
+ resources = File.read file_resources
145
141
  expect(resources.scan(/\n/).length).to be > 1
146
142
  end
147
143
  context "resources files" do
148
- let(:resources) { FILE_RESOURCES.read }
144
+ let(:resources) { file_resources.read }
149
145
  let(:response) { JSON.parse(resources) }
150
146
  it "writes version correctly" do
151
147
  expect(response["apiVersion"]).to eq DEFAULT_VER
@@ -167,11 +163,9 @@ describe Swagger::Docs::Generator do
167
163
  end
168
164
  end
169
165
  context "resource file" do
170
- let(:resource) { FILE_RESOURCE.read }
166
+ let(:resource) { file_resource.read }
171
167
  let(:response) { JSON.parse(resource) }
172
- let(:operations) { api["operations"] }
173
- let(:params) { operations.first["parameters"] }
174
- let(:response_msgs) { operations.first["responseMessages"] }
168
+ let(:apis) { response["apis"] }
175
169
  # {"apiVersion":"1.0","swaggerVersion":"1.2","basePath":"/api/v1","resourcePath":"/sample"
176
170
  it "writes version correctly" do
177
171
  expect(response["apiVersion"]).to eq DEFAULT_VER
@@ -186,75 +180,124 @@ describe Swagger::Docs::Generator do
186
180
  expect(response["resourcePath"]).to eq "sample"
187
181
  end
188
182
  it "writes out expected api count" do
189
- expect(response["apis"].count).to eq 6
183
+ expect(response["apis"].count).to eq 7
190
184
  end
191
- context "first api" do
192
- let(:api) { response["apis"][0] }
193
- #"apis":[{"path":" /sample","operations":[{"summary":"Fetches all User items"
194
- #,"method":"get","nickname":"Api::V1::Sample#index"}]
195
- it "writes path correctly when api extension type is not set" do
196
- expect(api["path"]).to eq "sample"
197
- end
198
- it "writes path correctly when api extension type is set" do
199
- config[DEFAULT_VER][:api_extension_type] = :json
200
- generate(config)
201
- expect(api["path"]).to eq "sample.json"
202
- end
203
- it "writes summary correctly" do
204
- expect(operations.first["summary"]).to eq "Fetches all User items"
205
- end
206
- it "writes method correctly" do
207
- expect(operations.first["method"]).to eq "get"
208
- end
209
- it "writes nickname correctly" do
210
- expect(operations.first["nickname"]).to eq "Api::V1::Sample#index"
211
- end
212
- #"parameters"=>[
213
- # {"paramType"=>"query", "name"=>"page", "type"=>"integer", "description"=>"Page number", "required"=>false},
214
- # {"paramType"=>"path", "name"=>"nested_id", "type"=>"integer", "description"=>"Team Id", "required"=>false}], "responseMessages"=>[{"code"=>401, "message"=>"Unauthorized"}, {"code"=>406, "message"=>"The request you made is not acceptable"}, {"code"=>416, "message"=>"Requested Range Not Satisfiable"}], "method"=>"get", "nickname"=>"Api::V1::Sample#index"}
215
- #]
216
- context "parameters" do
217
- it "has correct count" do
218
- expect(params.count).to eq 1
185
+ context "apis" do
186
+ context "index" do
187
+ let(:api) { get_api_operation(apis, "sample", :get) }
188
+ let(:operations) { get_api_operations(apis, "sample") }
189
+ #"apis":[{"path":" /sample","operations":[{"summary":"Fetches all User items"
190
+ #,"method":"get","nickname":"Api::V1::Sample#index"}]
191
+ it "writes path correctly when api extension type is not set" do
192
+ expect(apis.first["path"]).to eq "sample"
219
193
  end
220
- it "writes paramType correctly" do
221
- expect(params.first["paramType"]).to eq "query"
194
+ it "writes path correctly when api extension type is set" do
195
+ config[DEFAULT_VER][:api_extension_type] = :json
196
+ generate(config)
197
+ expect(apis.first["path"]).to eq "sample.json"
222
198
  end
223
- it "writes name correctly" do
224
- expect(params.first["name"]).to eq "page"
199
+ it "writes summary correctly" do
200
+ expect(operations.first["summary"]).to eq "Fetches all User items"
225
201
  end
226
- it "writes type correctly" do
227
- expect(params.first["type"]).to eq "integer"
202
+ it "writes method correctly" do
203
+ expect(operations.first["method"]).to eq "get"
228
204
  end
229
- it "writes description correctly" do
230
- expect(params.first["description"]).to eq "Page number"
205
+ it "writes nickname correctly" do
206
+ expect(operations.first["nickname"]).to eq "Api::V1::Sample#index"
231
207
  end
232
- it "writes required correctly" do
233
- expect(params.first["required"]).to be_false
208
+ #"parameters"=>[
209
+ # {"paramType"=>"query", "name"=>"page", "type"=>"integer", "description"=>"Page number", "required"=>false},
210
+ # {"paramType"=>"path", "name"=>"nested_id", "type"=>"integer", "description"=>"Team Id", "required"=>false}], "responseMessages"=>[{"code"=>401, "message"=>"Unauthorized"}, {"code"=>406, "message"=>"The request you made is not acceptable"}, {"code"=>416, "message"=>"Requested Range Not Satisfiable"}], "method"=>"get", "nickname"=>"Api::V1::Sample#index"}
211
+ #]
212
+ context "parameters" do
213
+ let(:params) { operations.first["parameters"] }
214
+ it "has correct count" do
215
+ expect(params.count).to eq 1
216
+ end
217
+ it "writes paramType correctly" do
218
+ expect(params.first["paramType"]).to eq "query"
219
+ end
220
+ it "writes name correctly" do
221
+ expect(params.first["name"]).to eq "page"
222
+ end
223
+ it "writes type correctly" do
224
+ expect(params.first["type"]).to eq "integer"
225
+ end
226
+ it "writes description correctly" do
227
+ expect(params.first["description"]).to eq "Page number"
228
+ end
229
+ it "writes required correctly" do
230
+ expect(params.first["required"]).to be_false
231
+ end
234
232
  end
235
- end
236
- #"responseMessages":[{"code":401,"message":"Unauthorized"},{"code":406,"message":"Not Acceptable"},{"code":416,"message":"Requested Range Not Satisfiable"}]
237
- context "response messages" do
238
- it "has correct count" do
239
- expect(response_msgs.count).to eq 3
233
+ #"responseMessages":[{"code":401,"message":"Unauthorized"},{"code":406,"message":"Not Acceptable"},{"code":416,"message":"Requested Range Not Satisfiable"}]
234
+ context "response messages" do
235
+ let(:response_msgs) { operations.first["responseMessages"] }
236
+ it "has correct count" do
237
+ expect(response_msgs.count).to eq 3
238
+ end
239
+ it "writes code correctly" do
240
+ expect(response_msgs.first["code"]).to eq 401
241
+ end
242
+ it "writes message correctly" do
243
+ expect(response_msgs.first["message"]).to eq "Unauthorized"
244
+ end
245
+ it "writes specified message correctly" do
246
+ expect(response_msgs[1]["message"]).to eq "The request you made is not acceptable"
247
+ end
240
248
  end
241
- it "writes code correctly" do
242
- expect(response_msgs.first["code"]).to eq 401
249
+ end
250
+ context "show" do
251
+ let(:api) { get_api_operation(apis, "nested/{nested_id}/sample", :get) }
252
+ let(:operations) { get_api_operations(apis, "nested/{nested_id}/sample") }
253
+ context "parameters" do
254
+ it "has correct count" do
255
+ expect(api["parameters"].count).to eq 2
256
+ end
243
257
  end
244
- it "writes message correctly" do
245
- expect(response_msgs.first["message"]).to eq "Unauthorized"
258
+ end
259
+ context "create" do
260
+ let(:api) { get_api_operation(apis, "sample", :post) }
261
+ it "writes list parameter values correctly" do
262
+ expected_param = {"valueType"=>"LIST", "values"=>["admin", "superadmin", "user"]}
263
+ expect(get_api_parameter(api, "role")["allowableValues"]).to eq expected_param
246
264
  end
247
- it "writes specified message correctly" do
248
- expect(response_msgs[1]["message"]).to eq "The request you made is not acceptable"
265
+ end
266
+ context "update" do
267
+ let(:api) { get_api_operation(apis, "sample/{id}", :put) }
268
+ it "writes model param correctly" do
269
+ expected_param = {
270
+ "paramType" => "form",
271
+ "name" => "tag",
272
+ "type" => "Tag",
273
+ "description" => "Tag object",
274
+ "required" => true,
275
+ }
276
+ expect(get_api_parameter(api, "tag")).to eq expected_param
249
277
  end
250
278
  end
251
279
  end
252
- context "second api (nested)" do
253
- let(:api) { response["apis"][1] }
254
- context "parameters" do
255
- it "has correct count" do
256
- expect(params.count).to eq 2
257
- end
280
+ context "models" do
281
+ let(:models) { response["models"] }
282
+ # Based on https://github.com/wordnik/swagger-core/wiki/Datatypes
283
+ it "writes model correctly" do
284
+ expected_model = {
285
+ "id" => "Tag",
286
+ "required" => ["id"],
287
+ "description" => "A Tag object.",
288
+ "properties" => {
289
+ "name" => {
290
+ "type" => "string",
291
+ "description" => "Name",
292
+ "foo" => "test",
293
+ },
294
+ "id" => {
295
+ "type" => "integer",
296
+ "description" => "User Id",
297
+ }
298
+ }
299
+ }
300
+ expect(models['Tag']).to eq expected_model
258
301
  end
259
302
  end
260
303
  end
data/spec/spec_helper.rb CHANGED
@@ -6,14 +6,39 @@ require 'pathname'
6
6
 
7
7
  DEFAULT_VER = Swagger::Docs::Generator::DEFAULT_VER
8
8
 
9
- TMP_DIR = Pathname.new "/tmp/swagger-docs/"
10
- TMP_API_DIR = TMP_DIR+"api/v1"
11
- FILE_RESOURCES = TMP_API_DIR+"api-docs.json"
12
- FILE_RESOURCE = TMP_API_DIR+"sample.json"
13
-
14
9
  RSpec.configure do |config|
15
10
  config.expect_with :rspec do |c|
16
11
  c.syntax = :expect
17
12
  end
18
13
  config.color_enabled = true
19
14
  end
15
+
16
+ def generate(config)
17
+ Swagger::Docs::Generator::write_docs(config)
18
+ end
19
+
20
+ def stub_route(verb, action, controller, spec)
21
+ double("route", :verb => double("verb", :source => verb),
22
+ :defaults => {:action => action, :controller => controller},
23
+ :path => double("path", :spec => spec)
24
+ )
25
+ end
26
+
27
+ def get_api_paths(apis, path)
28
+ apis.select{|api| api["path"] == path}
29
+ end
30
+
31
+ def get_api_operations(apis, path)
32
+ apis = get_api_paths(apis, path)
33
+ apis.collect{|api| api["operations"]}.flatten
34
+ end
35
+
36
+ def get_api_operation(apis, path, method)
37
+ operations = get_api_operations(apis, path)
38
+ operations.each{|operation| return operation if operation["method"] == method.to_s}
39
+ end
40
+
41
+ def get_api_parameter(api, name)
42
+ api["parameters"].each{|param| return param if param["name"] == name}
43
+ end
44
+
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: swagger-docs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rich Hollis
@@ -30,7 +30,7 @@ cert_chain:
30
30
  RYcsqDfanYBx7QcftOnbeQq7/Ep7Zx+W9+Ph3TiJLMLdAr7bLkgN1SjvrjTL5mQR
31
31
  FuQtYvE4LKiUQpG7vLTRB78dQBlSj9fnv2OM9w==
32
32
  -----END CERTIFICATE-----
33
- date: 2014-02-14 00:00:00.000000000 Z
33
+ date: 2014-04-02 00:00:00.000000000 Z
34
34
  dependencies:
35
35
  - !ruby/object:Gem::Dependency
36
36
  name: bundler
metadata.gz.sig CHANGED
@@ -1,3 +1 @@
1
- D���v�v���"���o���/C˞�/rc��HU�>*7�6�T;UCap��F4H��j��#u�|5�V���nt0��������ن�Γ���L5�G���cΞ�j������=t �悰[B��I۾�:+76�}���[ݙcXL���͸v�����dl�
2
- 6`�����ѽ��m����p���֌��������l��+�XK|`1�ds�B�{ɫœ1'�.m��i��f�7�qY��
3
- ��tpS��
1
+ ��Í��%��eb�D�',�gk��yf�8_*�@��v@H4ȴ �)s�ҥ�?Ȏ�aQ��UO�'��|�|*� ��Z�p���/ݾ7�gL���L*����u��w�LƗ�� F\�8���?��_�h��6���R1Y�?m��dp��b)>ܝكZa��,�p;pw��e@W��p��0�Ӵ�[ʦo �����hMK��}�G�2��LN���*��Jz)�9-�w��4�e+�3ϝ�>��/�?xc�Ѧ�A}