openapi-rails 0.3.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.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/Gemfile +3 -0
  4. data/Gemfile.lock +99 -0
  5. data/LICENSE.md +21 -0
  6. data/README.md +61 -0
  7. data/Rakefile +1 -0
  8. data/app/assets/fonts/openapi/DroidSans-Bold.ttf +0 -0
  9. data/app/assets/fonts/openapi/DroidSans.ttf +0 -0
  10. data/app/assets/images/openapi/collapse.gif +0 -0
  11. data/app/assets/images/openapi/expand.gif +0 -0
  12. data/app/assets/images/openapi/explorer_icons.png +0 -0
  13. data/app/assets/images/openapi/favicon-16x16.png +0 -0
  14. data/app/assets/images/openapi/favicon-32x32.png +0 -0
  15. data/app/assets/images/openapi/favicon.ico +0 -0
  16. data/app/assets/images/openapi/logo_small.png +0 -0
  17. data/app/assets/images/openapi/pet_store_api.png +0 -0
  18. data/app/assets/images/openapi/throbber.gif +0 -0
  19. data/app/assets/images/openapi/wordnik_api.png +0 -0
  20. data/app/assets/javascripts/openapi/application.coffee +58 -0
  21. data/app/assets/javascripts/openapi/lib/backbone-min.js +15 -0
  22. data/app/assets/javascripts/openapi/lib/es5-shim.js +2065 -0
  23. data/app/assets/javascripts/openapi/lib/handlebars-4.0.5.js +4608 -0
  24. data/app/assets/javascripts/openapi/lib/highlight.9.1.0.pack.js +2 -0
  25. data/app/assets/javascripts/openapi/lib/highlight.9.1.0.pack_extended.js +34 -0
  26. data/app/assets/javascripts/openapi/lib/jquery-1.8.0.min.js +2 -0
  27. data/app/assets/javascripts/openapi/lib/jquery.ba-bbq.min.js +18 -0
  28. data/app/assets/javascripts/openapi/lib/jquery.slideto.min.js +1 -0
  29. data/app/assets/javascripts/openapi/lib/jquery.wiggle.min.js +8 -0
  30. data/app/assets/javascripts/openapi/lib/js-yaml.min.js +3 -0
  31. data/app/assets/javascripts/openapi/lib/jsoneditor.min.js +11 -0
  32. data/app/assets/javascripts/openapi/lib/lodash.min.js +102 -0
  33. data/app/assets/javascripts/openapi/lib/marked.js +1272 -0
  34. data/app/assets/javascripts/openapi/lib/object-assign-pollyfill.js +23 -0
  35. data/app/assets/javascripts/openapi/lib/swagger-oauth.js +347 -0
  36. data/app/assets/javascripts/openapi/swagger-ui.js +24758 -0
  37. data/app/assets/stylesheets/openapi/application.scss +4 -0
  38. data/app/assets/stylesheets/openapi/print.scss +1367 -0
  39. data/app/assets/stylesheets/openapi/reset.scss +125 -0
  40. data/app/assets/stylesheets/openapi/screen.scss +1497 -0
  41. data/app/assets/stylesheets/openapi/typography.scss +14 -0
  42. data/app/controllers/openapi_controller.rb +24 -0
  43. data/app/views/openapi/index.html.erb +56 -0
  44. data/lib/generators/openapi/config_generator.rb +20 -0
  45. data/lib/generators/openapi/templates/base_controller.rb +6 -0
  46. data/lib/generators/openapi/templates/openapi.rb +21 -0
  47. data/lib/openapi-rails.rb +1 -0
  48. data/lib/openapi.rb +26 -0
  49. data/lib/openapi/configuration.rb +17 -0
  50. data/lib/openapi/engine.rb +32 -0
  51. data/lib/openapi/mongoid/crud_actions.rb +235 -0
  52. data/lib/openapi/mongoid/spec_builder.rb +451 -0
  53. data/lib/openapi/routes_parser.rb +50 -0
  54. data/lib/openapi/version.rb +3 -0
  55. data/lib/rails/routes.rb +20 -0
  56. data/lib/renderers/csv.rb +36 -0
  57. data/lib/swagger/blocks/items_node.rb +7 -0
  58. data/lib/swagger/blocks/property_node.rb +7 -0
  59. data/lib/swagger/blocks/schema_builder.rb +89 -0
  60. data/lib/swagger/blocks/schema_node.rb +7 -0
  61. data/openapi-rails.gemspec +35 -0
  62. metadata +204 -0
@@ -0,0 +1,451 @@
1
+ module Openapi
2
+ class SwaggerRoot
3
+ include Swagger::Blocks
4
+
5
+ def self.build_specification(config, controller_classes)
6
+ schema = Rails.env.production? ? 'https' : 'http'
7
+
8
+ swagger_root do
9
+ key :swagger, '2.0'
10
+ key :host, ENV['HOST'] || 'localhost:3000'
11
+ key :basePath, config[:base_path] || '/api'
12
+ key :consumes, %w(application/json)
13
+ key :produces, %w(application/json text/csv)
14
+ key :schemes, [schema]
15
+
16
+ info do
17
+ key :title, config[:title] || 'Default'
18
+ key :description, config[:description] || ''
19
+ key :version, config[:version] || '1.0'
20
+ end
21
+
22
+ controller_classes.each do |c|
23
+ tag do
24
+ key :name, c.openapi_collection_name
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ module Mongoid
32
+ module SpecBuilder
33
+ extend ActiveSupport::Concern
34
+
35
+ CRUD_ACTIONS = %w(index create show update destroy).freeze
36
+
37
+ included do
38
+ include Swagger::Blocks
39
+
40
+ class_attribute :openapi_collection_name
41
+ class_attribute :openapi_resource_name
42
+ class_attribute :openapi_resource_class
43
+ class_attribute :openapi_except_actions
44
+ class_attribute :openapi_relative_path
45
+
46
+ class_attribute :openapi_base_path
47
+ end
48
+
49
+ class_methods do
50
+ def openapi_config(options)
51
+ self.openapi_collection_name = options[:collection_name]
52
+ self.openapi_resource_name = options[:resource_name]
53
+ self.openapi_resource_class = options[:resource_class]
54
+ self.openapi_except_actions = options[:except_actions]
55
+ self.openapi_relative_path = options[:relative_path]
56
+ end
57
+
58
+ def build_openapi_specification(options)
59
+ self.openapi_base_path = options[:base_path]
60
+
61
+ self.openapi_relative_path ||=
62
+ ('/' + to_s.remove(/Controller$/).gsub('::', '/').underscore).
63
+ remove(openapi_base_path)
64
+
65
+ self.openapi_except_actions ||= []
66
+
67
+ self.openapi_collection_name ||=
68
+ to_s.split('::').last.sub(/Controller$/, '')
69
+
70
+ self.openapi_resource_name ||=
71
+ self.openapi_collection_name.singularize
72
+
73
+ self.openapi_resource_class ||= self.try(:resource_class)
74
+ self.openapi_resource_class ||=
75
+ self.openapi_resource_name.constantize
76
+
77
+ build_openapi_definitions
78
+ build_openapi_paths
79
+ end
80
+
81
+ def build_openapi_paths
82
+ routes = Openapi::RoutesParser.new(self).routes
83
+ build_crud_specification(routes)
84
+
85
+ if Rails.env.development?
86
+ warn_on_undocumented_actions(routes)
87
+ end
88
+ end
89
+
90
+ def build_openapi_definitions
91
+ collection_name = openapi_collection_name
92
+ resource_class = openapi_resource_class
93
+ resource_name = openapi_resource_name
94
+ resource_property_name = resource_name.underscore.to_sym
95
+
96
+ swagger_schema resource_name do
97
+ build_model_schema(resource_class)
98
+
99
+ resource_class.relations.each do |key, relation|
100
+ relation_type = relation.relation.to_s
101
+
102
+ if relation_type.include? 'Mongoid::Relations::Embedded'
103
+ relation_name =
104
+ relation.name.to_s.singularize.titleize.remove(' ')
105
+
106
+ embedded_resource_class =
107
+ (relation.class_name || relation_name).constantize
108
+
109
+ if relation_type.include? 'Many'
110
+ property relation.name.to_s, type: :array do
111
+ items do
112
+ build_model_schema(embedded_resource_class)
113
+ end
114
+ end
115
+
116
+ else
117
+ property relation.name.to_s.singularize, type: :object do
118
+ build_model_schema(embedded_resource_class)
119
+ end
120
+
121
+ end
122
+ end
123
+ end
124
+ end
125
+
126
+ swagger_schema "#{resource_name}Input" do
127
+ property resource_property_name, type: :object do
128
+ build_model_schema(resource_class, true)
129
+ end
130
+ end
131
+ end
132
+
133
+ def build_crud_specification(routes)
134
+ name = openapi_resource_name
135
+ sym_name = name.underscore.to_sym
136
+ plural_name = openapi_collection_name
137
+ path = openapi_relative_path
138
+ scopes = try(:scopes_configuration) || []
139
+ actions = routes.map {|r| r[2]}.uniq
140
+ json_mime = %w(application/json)
141
+
142
+ include_index = actions.include?('index') &&
143
+ !openapi_except_actions.include?('index')
144
+ include_create = actions.include?('create') &&
145
+ !openapi_except_actions.include?('create')
146
+ include_show = actions.include?('show') &&
147
+ !openapi_except_actions.include?('show')
148
+ include_update = actions.include?('update') &&
149
+ !openapi_except_actions.include?('update')
150
+ include_destroy = actions.include?('destroy') &&
151
+ !openapi_except_actions.include?('destroy')
152
+
153
+ include_collection_actions =
154
+ (include_index || include_create)
155
+ include_resource_actions =
156
+ (include_show || include_update || include_destroy)
157
+
158
+ support_search = openapi_resource_class.instance_methods(false)
159
+ .include?(:search)
160
+
161
+ if include_collection_actions
162
+ swagger_path path do
163
+
164
+ if include_index
165
+ operation :get do
166
+ key :tags, [plural_name]
167
+ key :summary, 'Index'
168
+ key :operationId, "index#{plural_name}"
169
+ key :produces, json_mime
170
+
171
+ parameter do
172
+ key :name, :page
173
+ key :description, 'Page number'
174
+ key :type, :integer
175
+ key :format, :int32
176
+ key :in, :query
177
+ key :required, false
178
+ end
179
+
180
+ parameter do
181
+ key :name, :perPage
182
+ key :description, 'Items per page'
183
+ key :type, :integer
184
+ key :format, :int32
185
+ key :in, :query
186
+ key :required, false
187
+ end
188
+
189
+ parameter do
190
+ key :name, :fields
191
+ key :in, :query
192
+ key :required, false
193
+ key :description, 'Return exact model fields'
194
+ key :type, :array
195
+ items do
196
+ key :type, :string
197
+ end
198
+ end
199
+
200
+ parameter do
201
+ key :name, :methods
202
+ key :description, 'Include model methods'
203
+ key :in, :query
204
+ key :required, false
205
+ key :type, :array
206
+ items do
207
+ key :type, :string
208
+ end
209
+ end
210
+
211
+ if support_search
212
+ parameter do
213
+ key :name, :search
214
+ key :description, 'Search query string'
215
+ key :type, :string
216
+ key :in, :query
217
+ key :required, false
218
+ end
219
+ end
220
+
221
+ scopes.each do |k, config|
222
+ scope_name = config[:as]
223
+ scope_type = config[:type]
224
+
225
+ if scope_type == :default
226
+ scope_type = :string
227
+ end
228
+
229
+ parameter do
230
+ key :name, scope_name
231
+ key :type, scope_type
232
+ key :in, :query
233
+ key :required, false
234
+
235
+ if scope_type == :integer
236
+ key :format, :int32
237
+ end
238
+ end
239
+ end
240
+
241
+ response 200 do
242
+ schema type: :array do
243
+ items do
244
+ key :'$ref', name
245
+ end
246
+ end
247
+ end
248
+ end
249
+ end
250
+
251
+ if include_create
252
+ operation :post do
253
+ key :tags, [plural_name]
254
+ key :summary, 'Create'
255
+ key :operationId, "create#{plural_name}"
256
+ key :produces, json_mime
257
+
258
+ parameter do
259
+ key :name, "body{#{sym_name}}"
260
+ key :in, :body
261
+ key :required, true
262
+ schema do
263
+ key :'$ref', "#{name}Input"
264
+ end
265
+ end
266
+
267
+ parameter do
268
+ key :name, :fields
269
+ key :in, :query
270
+ key :required, false
271
+ key :description, 'Return exact model fields'
272
+ key :type, :array
273
+ items do
274
+ key :type, :string
275
+ end
276
+ end
277
+
278
+ parameter do
279
+ key :name, :methods
280
+ key :description, 'Include model methods'
281
+ key :in, :query
282
+ key :required, false
283
+ key :type, :array
284
+ items do
285
+ key :type, :string
286
+ end
287
+ end
288
+
289
+ response 201 do
290
+ schema do
291
+ key :'$ref', name
292
+ end
293
+ end
294
+ end
295
+ end
296
+ end
297
+ end
298
+
299
+ if include_resource_actions
300
+ swagger_path "#{path}/{id}" do
301
+
302
+ if include_show
303
+ operation :get do
304
+ key :tags, [plural_name]
305
+ key :summary, 'Show'
306
+ key :operationId, "show#{name}ById"
307
+ key :produces, json_mime
308
+
309
+ parameter do
310
+ key :name, :id
311
+ key :type, :string
312
+ key :in, :path
313
+ key :required, true
314
+ end
315
+
316
+ parameter do
317
+ key :name, :fields
318
+ key :in, :query
319
+ key :required, false
320
+ key :description, 'Return exact model fields'
321
+ key :type, :array
322
+ items do
323
+ key :type, :string
324
+ end
325
+ end
326
+
327
+ parameter do
328
+ key :name, :methods
329
+ key :description, 'Include model methods'
330
+ key :in, :query
331
+ key :required, false
332
+ key :type, :array
333
+ items do
334
+ key :type, :string
335
+ end
336
+ end
337
+
338
+ response 200 do
339
+ schema do
340
+ key :'$ref', name
341
+ end
342
+ end
343
+ end
344
+ end
345
+
346
+ if include_update
347
+ operation :put do
348
+ key :tags, [plural_name]
349
+ key :summary, 'Update'
350
+ key :operationId, "update#{name}"
351
+ key :produces, json_mime
352
+
353
+ parameter do
354
+ key :name, :id
355
+ key :type, :string
356
+ key :in, :path
357
+ key :required, true
358
+ end
359
+
360
+ parameter do
361
+ key :name, :fields
362
+ key :in, :query
363
+ key :required, false
364
+ key :description, 'Return exact model fields'
365
+ key :type, :array
366
+ items do
367
+ key :type, :string
368
+ end
369
+ end
370
+
371
+ parameter do
372
+ key :name, :methods
373
+ key :description, 'Include model methods'
374
+ key :in, :query
375
+ key :required, false
376
+ key :type, :array
377
+ items do
378
+ key :type, :string
379
+ end
380
+ end
381
+
382
+ parameter do
383
+ key :name, "body{#{sym_name}}"
384
+ key :in, :body
385
+ key :required, true
386
+ schema do
387
+ key :'$ref', "#{name}Input"
388
+ end
389
+ end
390
+
391
+ response 200 do
392
+ schema do
393
+ key :'$ref', name
394
+ end
395
+ end
396
+ end
397
+ end
398
+
399
+ if include_destroy
400
+ operation :delete do
401
+ key :tags, [plural_name]
402
+ key :summary, 'Destroy'
403
+ key :operationId, "destroy#{name}"
404
+
405
+ parameter do
406
+ key :name, :id
407
+ key :type, :string
408
+ key :in, :path
409
+ key :required, true
410
+ end
411
+
412
+ response 204 do
413
+ key :description, "#{name} destroyed"
414
+ end
415
+ end
416
+ end
417
+ end
418
+ end
419
+ end
420
+
421
+ def warn_on_undocumented_actions(routes)
422
+ custom_routes = routes.select {|r| !CRUD_ACTIONS.include?(r[2])}
423
+ no_spec_methods = custom_routes.select do |route|
424
+ method = route[0].to_sym
425
+ path = route[1].remove(openapi_base_path)
426
+ path_sym = path.gsub(/:(\w+)/, '{\1}').to_sym
427
+
428
+ ! action_specification_exists?(method, path_sym)
429
+ end
430
+
431
+ unless no_spec_methods.empty?
432
+ routes = no_spec_methods.map do |r|
433
+ method = r[0].upcase
434
+ path = r[1]
435
+ " #{method} #{path}"
436
+ end.join("\n")
437
+
438
+ puts "\n#{self} misses specification for:\n#{routes}\n\n"
439
+ end
440
+ end
441
+
442
+ def action_specification_exists?(method, path)
443
+ swagger_nodes = self.send(:_swagger_nodes)
444
+ node_map = swagger_nodes[:path_node_map]
445
+
446
+ node_map.has_key?(path) && node_map[path].data.has_key?(method)
447
+ end
448
+ end
449
+ end
450
+ end
451
+ end
@@ -0,0 +1,50 @@
1
+ module Openapi
2
+ class RoutesParser
3
+ require 'action_dispatch/routing/inspector'
4
+
5
+ attr_accessor :routes
6
+
7
+ def initialize(controller)
8
+ @controller = controller
9
+ @routes = []
10
+
11
+ formatter = ActionDispatch::Routing::ConsoleFormatter.new
12
+ @routes_table = routes_inspector.format(formatter, controller_slug)
13
+
14
+ parse!
15
+ end
16
+
17
+ private
18
+
19
+ def rails_routes
20
+ Rails.application.routes.routes
21
+ end
22
+
23
+ def routes_inspector
24
+ ActionDispatch::Routing::RoutesInspector.new(rails_routes)
25
+ end
26
+
27
+ def controller_slug
28
+ @controller.
29
+ to_s.
30
+ underscore.
31
+ gsub('::', '/').
32
+ gsub('_controller','')
33
+ end
34
+
35
+ def parse!
36
+ @routes_table = @routes_table.split("\n")
37
+ @routes_table.shift
38
+
39
+ @routes_table.each do |row|
40
+ row.remove! ' {:format=>:json}'
41
+ action = row.sub(/.*?#/, '')
42
+ route = row.split(' ').reverse
43
+ path = route[1].gsub('(.:format)','')
44
+ method = route[2].underscore
45
+
46
+ @routes << [method, path, action]
47
+ end
48
+ end
49
+ end
50
+ end