swagger-docs 0.1.1 → 0.1.2

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
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}