apipie-rails 0.3.6 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (136) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/build.yml +67 -0
  3. data/.github/workflows/rubocop-challenger.yml +28 -0
  4. data/.gitignore +2 -0
  5. data/.rubocop.yml +37 -0
  6. data/.rubocop_todo.yml +1991 -0
  7. data/CHANGELOG.md +246 -2
  8. data/PROPOSAL_FOR_RESPONSE_DESCRIPTIONS.md +244 -0
  9. data/README.rst +646 -25
  10. data/Rakefile +0 -5
  11. data/apipie-rails.gemspec +14 -9
  12. data/app/controllers/apipie/apipies_controller.rb +51 -20
  13. data/app/public/apipie/javascripts/bundled/bootstrap-collapse.js +70 -41
  14. data/app/public/apipie/javascripts/bundled/bootstrap.js +1033 -479
  15. data/app/public/apipie/javascripts/bundled/jquery.js +5 -5
  16. data/app/public/apipie/stylesheets/bundled/bootstrap-responsive.min.css +9 -12
  17. data/app/public/apipie/stylesheets/bundled/bootstrap.min.css +9 -689
  18. data/app/views/apipie/apipies/_method_detail.erb +21 -0
  19. data/app/views/apipie/apipies/_params.html.erb +4 -2
  20. data/app/views/apipie/apipies/index.html.erb +5 -1
  21. data/app/views/apipie/apipies/resource.html.erb +3 -0
  22. data/app/views/layouts/apipie/apipie.html.erb +1 -1
  23. data/config/locales/en.yml +1 -0
  24. data/config/locales/fr.yml +31 -0
  25. data/config/locales/it.yml +31 -0
  26. data/config/locales/ja.yml +31 -0
  27. data/config/locales/ko.yml +31 -0
  28. data/config/locales/pt-BR.yml +1 -1
  29. data/gemfiles/Gemfile.rails50 +10 -0
  30. data/gemfiles/Gemfile.rails51 +10 -0
  31. data/gemfiles/Gemfile.rails52 +10 -0
  32. data/gemfiles/Gemfile.rails60 +17 -0
  33. data/gemfiles/Gemfile.rails61 +17 -0
  34. data/gemfiles/Gemfile.rails70 +17 -0
  35. data/lib/apipie/apipie_module.rb +22 -4
  36. data/lib/apipie/application.rb +54 -25
  37. data/lib/apipie/configuration.rb +26 -4
  38. data/lib/apipie/core_ext/route.rb +9 -0
  39. data/lib/apipie/dsl_definition.rb +168 -16
  40. data/lib/apipie/error_description.rb +9 -2
  41. data/lib/apipie/errors.rb +34 -0
  42. data/lib/apipie/extractor/collector.rb +4 -0
  43. data/lib/apipie/extractor/recorder.rb +14 -12
  44. data/lib/apipie/extractor/writer.rb +86 -58
  45. data/lib/apipie/extractor.rb +5 -5
  46. data/lib/apipie/generator/generator.rb +2 -0
  47. data/lib/apipie/generator/swagger/swagger.rb +2 -0
  48. data/lib/apipie/generator/swagger/type.rb +16 -0
  49. data/lib/apipie/generator/swagger/type_extractor.rb +70 -0
  50. data/lib/apipie/generator/swagger/warning.rb +77 -0
  51. data/lib/apipie/generator/swagger/warning_writer.rb +48 -0
  52. data/lib/apipie/markup.rb +14 -11
  53. data/lib/apipie/method_description/api.rb +12 -0
  54. data/lib/apipie/method_description/apis_service.rb +82 -0
  55. data/lib/apipie/method_description.rb +51 -49
  56. data/lib/apipie/param_description.rb +63 -5
  57. data/lib/apipie/resource_description.rb +11 -4
  58. data/lib/apipie/response_description.rb +131 -0
  59. data/lib/apipie/response_description_adapter.rb +200 -0
  60. data/lib/apipie/routes_formatter.rb +1 -1
  61. data/lib/apipie/rspec/response_validation_helper.rb +194 -0
  62. data/lib/apipie/static_dispatcher.rb +5 -2
  63. data/lib/apipie/swagger_generator.rb +717 -0
  64. data/lib/apipie/tag_list_description.rb +11 -0
  65. data/lib/apipie/validator.rb +83 -9
  66. data/lib/apipie/version.rb +1 -1
  67. data/lib/apipie-rails.rb +15 -4
  68. data/lib/generators/apipie/install/install_generator.rb +1 -1
  69. data/lib/generators/apipie/views_generator.rb +1 -1
  70. data/lib/tasks/apipie.rake +115 -15
  71. data/rel-eng/gem_release.ipynb +398 -0
  72. data/spec/controllers/apipies_controller_spec.rb +79 -14
  73. data/spec/controllers/concerns_controller_spec.rb +2 -2
  74. data/spec/controllers/extended_controller_spec.rb +14 -0
  75. data/spec/controllers/included_param_group_controller_spec.rb +13 -0
  76. data/spec/controllers/memes_controller_spec.rb +10 -0
  77. data/spec/controllers/users_controller_spec.rb +139 -76
  78. data/spec/dummy/Rakefile +1 -1
  79. data/spec/dummy/app/controllers/application_controller.rb +5 -1
  80. data/spec/dummy/app/controllers/concerns_controller.rb +1 -1
  81. data/spec/dummy/app/controllers/extended_controller.rb +14 -0
  82. data/spec/dummy/app/controllers/extending_concern.rb +10 -0
  83. data/spec/dummy/app/controllers/included_param_group_controller.rb +19 -0
  84. data/spec/dummy/app/controllers/overridden_concerns_controller.rb +2 -2
  85. data/spec/dummy/app/controllers/pets_controller.rb +408 -0
  86. data/spec/dummy/app/controllers/pets_using_auto_views_controller.rb +73 -0
  87. data/spec/dummy/app/controllers/pets_using_self_describing_classes_controller.rb +95 -0
  88. data/spec/dummy/app/controllers/{concerns/sample_controller.rb → sample_controller.rb} +5 -7
  89. data/spec/dummy/app/controllers/tagged_cats_controller.rb +32 -0
  90. data/spec/dummy/app/controllers/tagged_dogs_controller.rb +15 -0
  91. data/spec/dummy/app/controllers/twitter_example_controller.rb +5 -0
  92. data/spec/dummy/app/controllers/users_controller.rb +26 -12
  93. data/spec/dummy/app/helpers/random_param_group.rb +8 -0
  94. data/spec/dummy/components/test_engine/Gemfile +6 -0
  95. data/spec/dummy/components/test_engine/app/controllers/test_engine/application_controller.rb +4 -0
  96. data/spec/dummy/components/test_engine/app/controllers/test_engine/memes_controller.rb +37 -0
  97. data/spec/dummy/components/test_engine/config/routes.rb +3 -0
  98. data/spec/dummy/components/test_engine/db/.gitkeep +0 -0
  99. data/spec/dummy/components/test_engine/lib/test_engine.rb +7 -0
  100. data/spec/dummy/components/test_engine/test_engine.gemspec +11 -0
  101. data/spec/dummy/config/application.rb +6 -4
  102. data/spec/dummy/config/boot.rb +2 -2
  103. data/spec/dummy/config/environment.rb +1 -1
  104. data/spec/dummy/config/environments/development.rb +3 -3
  105. data/spec/dummy/config/environments/production.rb +3 -3
  106. data/spec/dummy/config/environments/test.rb +3 -5
  107. data/spec/dummy/config/initializers/apipie.rb +5 -3
  108. data/spec/dummy/config/routes.rb +25 -1
  109. data/spec/dummy/config.ru +1 -1
  110. data/spec/dummy/script/rails +2 -2
  111. data/spec/lib/application_spec.rb +1 -1
  112. data/spec/lib/extractor/writer_spec.rb +37 -7
  113. data/spec/lib/file_handler_spec.rb +25 -0
  114. data/spec/lib/generator/swagger/type_extractor_spec.rb +61 -0
  115. data/spec/lib/generator/swagger/warning_spec.rb +51 -0
  116. data/spec/lib/generator/swagger/warning_writer_spec.rb +59 -0
  117. data/spec/lib/method_description/apis_service_spec.rb +60 -0
  118. data/spec/lib/method_description_spec.rb +34 -0
  119. data/spec/lib/param_description_spec.rb +90 -4
  120. data/spec/lib/rake_spec.rb +2 -4
  121. data/spec/lib/swagger/openapi_2_0_schema.json +1607 -0
  122. data/spec/lib/swagger/rake_swagger_spec.rb +154 -0
  123. data/spec/lib/swagger/response_validation_spec.rb +104 -0
  124. data/spec/lib/swagger/swagger_dsl_spec.rb +658 -0
  125. data/spec/lib/validator_spec.rb +59 -1
  126. data/spec/lib/validators/array_validator_spec.rb +28 -8
  127. data/spec/spec_helper.rb +49 -3
  128. data/spec/support/custom_bool_validator.rb +17 -0
  129. metadata +104 -99
  130. data/.travis.yml +0 -12
  131. data/Gemfile +0 -7
  132. data/Gemfile.rails32 +0 -6
  133. data/Gemfile.rails40 +0 -5
  134. data/Gemfile.rails41 +0 -5
  135. data/Gemfile.rails42 +0 -5
  136. data/lib/apipie/client/generator.rb +0 -135
@@ -2,12 +2,20 @@
2
2
 
3
3
  module Apipie
4
4
 
5
- # DSL is a module that provides #api, #error, #param, #error.
5
+ # DSL is a module that provides #api, #error, #param, #returns.
6
6
  module DSL
7
7
 
8
8
  module Base
9
9
  attr_reader :apipie_resource_descriptions, :api_params
10
10
 
11
+ def _apipie_eval_dsl(*args, &block)
12
+ raise 'The Apipie DLS data need to be cleared before evaluating new block' if @_apipie_dsl_data
13
+ instance_exec(*args, &block)
14
+ return _apipie_dsl_data
15
+ ensure
16
+ _apipie_dsl_data_clear
17
+ end
18
+
11
19
  private
12
20
 
13
21
  def _apipie_dsl_data
@@ -24,9 +32,11 @@ module Apipie
24
32
  :api_args => [],
25
33
  :api_from_routes => nil,
26
34
  :errors => [],
35
+ :tag_list => [],
36
+ :returns => {},
27
37
  :params => [],
28
38
  :headers => [],
29
- :resouce_id => nil,
39
+ :resource_id => nil,
30
40
  :short_description => nil,
31
41
  :description => nil,
32
42
  :examples => [],
@@ -34,7 +44,8 @@ module Apipie
34
44
  :formats => nil,
35
45
  :api_versions => [],
36
46
  :meta => nil,
37
- :show => true
47
+ :show => true,
48
+ :deprecated => false
38
49
  }
39
50
  end
40
51
  end
@@ -68,6 +79,11 @@ module Apipie
68
79
  def app_info(app_info)
69
80
  _apipie_dsl_data[:app_info] = app_info
70
81
  end
82
+
83
+ def deprecated(value)
84
+ _apipie_dsl_data[:deprecated] = value
85
+ end
86
+
71
87
  end
72
88
 
73
89
  module Action
@@ -123,7 +139,7 @@ module Apipie
123
139
  #
124
140
  # Example:
125
141
  # api :desc => "Show user profile", :path => "/users/", :version => '1.0 - 3.4.2012'
126
- # param :id, Fixnum, :desc => "User ID", :required => true
142
+ # param :id, Integer, :desc => "User ID", :required => true
127
143
  # desc <<-EOS
128
144
  # Long description...
129
145
  # EOS
@@ -133,10 +149,10 @@ module Apipie
133
149
 
134
150
  dsl_data = ResourceDescriptionDsl.eval_dsl(self, &block)
135
151
  versions = dsl_data[:api_versions]
152
+ Apipie.set_controller_versions(self, versions)
136
153
  @apipie_resource_descriptions = versions.map do |version|
137
154
  Apipie.define_resource_description(self, version, dsl_data)
138
155
  end
139
- Apipie.set_controller_versions(self, versions)
140
156
  end
141
157
  end
142
158
 
@@ -207,6 +223,13 @@ module Apipie
207
223
  _apipie_dsl_data[:errors] << [code_or_options, desc, options]
208
224
  end
209
225
 
226
+ # Add tags to resources and actions group operations together.
227
+ def tags(*args)
228
+ return unless Apipie.active_dsl?
229
+ tags = args.length == 1 ? args.first : args
230
+ _apipie_dsl_data[:tag_list] += tags
231
+ end
232
+
210
233
  def _apipie_define_validators(description)
211
234
 
212
235
  # [re]define method only if validation is turned on
@@ -237,9 +260,11 @@ module Apipie
237
260
  # Only allow params passed in that are defined keys in the api
238
261
  # Auto skip the default params (format, controller, action)
239
262
  if Apipie.configuration.validate_key?
240
- params.reject{|k,_| %w[format controller action].include?(k.to_s) }.each_key do |param|
263
+ params.reject{|k,_| %w[format controller action].include?(k.to_s) }.each_pair do |param, _|
241
264
  # params allowed
242
- raise UnknownParam.new(param) if method_params.select {|_,p| p.name.to_s == param.to_s}.empty?
265
+ if method_params.select {|_,p| p.name.to_s == param.to_s}.empty?
266
+ self.class._apipie_handle_validate_key_error params, param
267
+ end
243
268
  end
244
269
  end
245
270
 
@@ -253,7 +278,7 @@ module Apipie
253
278
  end
254
279
  end
255
280
 
256
- if (Apipie.configuration.validate == :implicitly || Apipie.configuration.validate == true)
281
+ if Apipie.configuration.validate == :implicitly || Apipie.configuration.validate == true
257
282
  old_method = instance_method(description.method)
258
283
 
259
284
  define_method(description.method) do |*args|
@@ -267,6 +292,15 @@ module Apipie
267
292
  end
268
293
  end
269
294
 
295
+ def _apipie_handle_validate_key_error params, param
296
+ if Apipie.configuration.action_on_non_validated_keys == :raise
297
+ raise UnknownParam, param
298
+ elsif Apipie.configuration.action_on_non_validated_keys == :skip
299
+ params.delete(param)
300
+ Rails.logger.warn(UnknownParam.new(param).to_s)
301
+ end
302
+ end
303
+
270
304
  def _apipie_save_method_params(method, params)
271
305
  @method_params ||= {}
272
306
  @method_params[method] = params
@@ -295,6 +329,7 @@ module Apipie
295
329
  end
296
330
  end
297
331
 
332
+
298
333
  # this describes the params, it's in separate module because it's
299
334
  # used in Validators as well
300
335
  module Param
@@ -315,10 +350,17 @@ module Apipie
315
350
  block]
316
351
  end
317
352
 
353
+ def property(param_name, validator, desc_or_options = nil, options = {}, &block) #:doc:
354
+ return unless Apipie.active_dsl?
355
+ options[:only_in] ||= :response
356
+ options[:required] = true if options[:required].nil?
357
+ param(param_name, validator, desc_or_options, options, &block)
358
+ end
359
+
318
360
  # Reuses param group for this method. The definition is looked up
319
361
  # in scope of this controller. If the group was defined in
320
362
  # different controller, the second param can be used to specify it.
321
- # when using action_aware parmas, you can specify :as =>
363
+ # when using action_aware params, you can specify :as =>
322
364
  # :create or :update to explicitly say how it should behave
323
365
  def param_group(name, scope_or_options = nil, options = {})
324
366
  if scope_or_options.is_a? Hash
@@ -340,6 +382,65 @@ module Apipie
340
382
  @_current_param_group = nil
341
383
  end
342
384
 
385
+ # Describe possible responses
386
+ #
387
+ # Example:
388
+ # def_param_group :user do
389
+ # param :user, Hash do
390
+ # param :name, String
391
+ # end
392
+ # end
393
+ #
394
+ # returns :user, "the speaker"
395
+ # returns "the speaker" do
396
+ # param_group: :user
397
+ # end
398
+ # returns :param_group => :user, "the speaker"
399
+ # returns :user, :code => 201, :desc => "the created speaker record"
400
+ # returns :array_of => :user, "many speakers"
401
+ # def hello_world
402
+ # render json: {user: {name: "Alfred"}}
403
+ # end
404
+ #
405
+ def returns(pgroup_or_options, desc_or_options=nil, options={}, &block) #:doc:
406
+ return unless Apipie.active_dsl?
407
+
408
+
409
+ if desc_or_options.is_a? Hash
410
+ options.merge!(desc_or_options)
411
+ elsif !desc_or_options.nil?
412
+ options[:desc] = desc_or_options
413
+ end
414
+
415
+ if pgroup_or_options.is_a? Hash
416
+ options.merge!(pgroup_or_options)
417
+ else
418
+ options[:param_group] = pgroup_or_options
419
+ end
420
+
421
+ code = options[:code] || 200
422
+ scope = options[:scope] || _default_param_group_scope
423
+ descriptor = options[:param_group] || options[:array_of]
424
+
425
+ if block.nil?
426
+ if descriptor.is_a? ResponseDescriptionAdapter
427
+ adapter = descriptor
428
+ elsif descriptor.respond_to? :describe_own_properties
429
+ adapter = ResponseDescriptionAdapter.from_self_describing_class(descriptor)
430
+ else
431
+ begin
432
+ block = Apipie.get_param_group(scope, descriptor) if descriptor
433
+ rescue
434
+ raise "No param_group or self-describing class named #{descriptor}"
435
+ end
436
+ end
437
+ elsif descriptor
438
+ raise "cannot specify both block and param_group"
439
+ end
440
+
441
+ _apipie_dsl_data[:returns][code] = [options, scope, block, adapter]
442
+ end
443
+
343
444
  # where the group definition should be looked up when no scope
344
445
  # given. This is expected to return a controller.
345
446
  def _default_param_group_scope
@@ -381,13 +482,58 @@ module Apipie
381
482
  _apipie_concern_subst.merge!(subst_hash)
382
483
  end
383
484
 
485
+ # Allows to update existing params after definition was made (usually needed
486
+ # when extending the API form plugins).
487
+ #
488
+ # UsersController.apipie_update_params([:create, :update]) do
489
+ # param :user, Hash do
490
+ # param :oauth, String
491
+ # end
492
+ # end
493
+ #
494
+ # The block is evaluated in scope of the controller. Ohe can pass some additional
495
+ # objects via additional arguments like this:
496
+ #
497
+ # UsersController.apipie_update_params([:create, :update], [:name, :secret]) do |param_names|
498
+ # param :user, Hash do
499
+ # param_names.each { |p| param p, String }
500
+ # end
501
+ # end
502
+ def _apipie_update_params(method_desc, dsl_data)
503
+ params_ordered = dsl_data[:params].map do |args|
504
+ Apipie::ParamDescription.from_dsl_data(method_desc, args)
505
+ end
506
+ ParamDescription.merge(method_desc.params_ordered_self, params_ordered)
507
+ end
508
+
509
+ def _apipie_update_meta(method_desc, dsl_data)
510
+ return unless dsl_data[:meta] && dsl_data[:meta].is_a?(Hash)
511
+
512
+ method_desc.metadata ||= {}
513
+ method_desc.metadata.merge!(dsl_data[:meta])
514
+ end
515
+
516
+ def apipie_update_methods(methods, *args, &block)
517
+ methods.each do |method|
518
+ method_desc = Apipie.get_method_description(self, method)
519
+ unless method_desc
520
+ raise "Could not find method description for #{self}##{method}. Was the method defined?"
521
+ end
522
+ dsl_data = _apipie_eval_dsl(*args, &block)
523
+ _apipie_update_params(method_desc, dsl_data)
524
+ _apipie_update_meta(method_desc, dsl_data)
525
+ end
526
+ end
527
+ # backwards compatibility
528
+ alias_method :apipie_update_params, :apipie_update_methods
529
+
384
530
  def _apipie_concern_subst
385
531
  @_apipie_concern_subst ||= {:controller_path => self.controller_path,
386
532
  :resource_id => Apipie.get_resource_name(self)}
387
533
  end
388
534
 
389
535
  def _apipie_perform_concern_subst(string)
390
- return _apipie_concern_subst.reduce(string) do |ret, (key, val)|
536
+ _apipie_concern_subst.reduce(string) do |ret, (key, val)|
391
537
  ret.gsub(":#{key}", val)
392
538
  end
393
539
  end
@@ -428,12 +574,19 @@ module Apipie
428
574
  description = Apipie.define_method_description(controller, method_name, _apipie_dsl_data)
429
575
  controller._apipie_define_validators(description)
430
576
  end
577
+ _apipie_concern_update_api_blocks.each do |(methods, block)|
578
+ controller.apipie_update_methods(methods, &block)
579
+ end
431
580
  end
432
581
 
433
582
  def _apipie_concern_data
434
583
  @_apipie_concern_data ||= []
435
584
  end
436
585
 
586
+ def _apipie_concern_update_api_blocks
587
+ @_apipie_concern_update_api_blocks ||= []
588
+ end
589
+
437
590
  def apipie_concern?
438
591
  true
439
592
  end
@@ -449,6 +602,10 @@ module Apipie
449
602
  _apipie_dsl_data_clear
450
603
  end
451
604
 
605
+ def update_api(*methods, &block)
606
+ _apipie_concern_update_api_blocks << [methods, block]
607
+ end
608
+
452
609
  end
453
610
 
454
611
  class ResourceDescriptionDsl
@@ -461,14 +618,9 @@ module Apipie
461
618
  @controller = controller
462
619
  end
463
620
 
464
- def _eval_dsl(&block)
465
- instance_eval(&block)
466
- return _apipie_dsl_data
467
- end
468
-
469
621
  # evaluates resource description DSL and returns results
470
622
  def self.eval_dsl(controller, &block)
471
- dsl_data = self.new(controller)._eval_dsl(&block)
623
+ dsl_data = self.new(controller)._apipie_eval_dsl(&block)
472
624
  if dsl_data[:api_versions].empty?
473
625
  dsl_data[:api_versions] = Apipie.controller_versions(controller)
474
626
  end
@@ -1,7 +1,6 @@
1
1
  module Apipie
2
2
 
3
3
  class ErrorDescription
4
-
5
4
  attr_reader :code, :description, :metadata
6
5
 
7
6
  def self.from_dsl_data(args)
@@ -18,7 +17,15 @@ module Apipie
18
17
  @metadata = code_or_options[:meta]
19
18
  @description = code_or_options[:desc] || code_or_options[:description]
20
19
  else
21
- @code = code_or_options
20
+ @code =
21
+ if code_or_options.is_a? Symbol
22
+ Rack::Utils::SYMBOL_TO_STATUS_CODE[code_or_options]
23
+ else
24
+ code_or_options
25
+ end
26
+
27
+ raise UnknownCode, code_or_options unless @code
28
+
22
29
  @metadata = options[:meta]
23
30
  @description = desc
24
31
  end
data/lib/apipie/errors.rb CHANGED
@@ -6,6 +6,15 @@ module Apipie
6
6
  class ParamError < Error
7
7
  end
8
8
 
9
+ class UnknownCode < Error
10
+ end
11
+
12
+ class ReturnsMultipleDefinitionError < Error
13
+ def to_s
14
+ "a 'returns' statement cannot indicate both array_of and type"
15
+ end
16
+ end
17
+
9
18
  # abstract
10
19
  class DefinedParamError < ParamError
11
20
  attr_accessor :param
@@ -49,4 +58,29 @@ module Apipie
49
58
  end
50
59
  end
51
60
 
61
+ class ResponseDoesNotMatchSwaggerSchema < Error
62
+ def initialize(controller_name, method_name, response_code, error_messages, schema, returned_object)
63
+ @controller_name = controller_name
64
+ @method_name = method_name
65
+ @response_code = response_code
66
+ @error_messages = error_messages
67
+ @schema = schema
68
+ @returned_object = returned_object
69
+ end
70
+
71
+ def to_s
72
+ "Response does not match swagger schema (#{@controller_name}##{@method_name} #{@response_code}): #{@error_messages}\nSchema: #{JSON(@schema)}\nReturned object: #{@returned_object}"
73
+ end
74
+ end
75
+
76
+ class NoDocumentedMethod < Error
77
+ def initialize(controller_name, method_name)
78
+ @method_name = method_name
79
+ @controller_name = controller_name
80
+ end
81
+
82
+ def to_s
83
+ "There is no documented method #{@controller_name}##{@method_name}"
84
+ end
85
+ end
52
86
  end
@@ -75,6 +75,10 @@ module Apipie
75
75
  if param_desc[:type].first == :number && (key.to_s !~ /id$/ || !Apipie::Validator::NumberValidator.validate(value))
76
76
  param_desc[:type].shift
77
77
  end
78
+
79
+ if param_desc[:type].first == :decimal && (key.to_s !~ /id$/ || !Apipie::Validator::DecimalValidator.validate(value))
80
+ param_desc[:type].shift
81
+ end
78
82
  end
79
83
 
80
84
  if value.is_a? Hash
@@ -13,12 +13,13 @@ module Apipie
13
13
  @query = env["QUERY_STRING"] unless env["QUERY_STRING"].blank?
14
14
  @params = Rack::Utils.parse_nested_query(@query)
15
15
  @params.merge!(env["action_dispatch.request.request_parameters"] || {})
16
- if data = parse_data(env["rack.input"].read)
16
+ rack_input = env["rack.input"]
17
+ if data = parse_data(rack_input.read)
17
18
  @request_data = data
18
- env["rack.input"].rewind
19
19
  elsif form_hash = env["rack.request.form_hash"]
20
20
  @request_data = reformat_multipart_data(form_hash)
21
21
  end
22
+ rack_input.rewind
22
23
  end
23
24
 
24
25
  def analyse_controller(controller)
@@ -28,7 +29,11 @@ module Apipie
28
29
 
29
30
  def analyse_response(response)
30
31
  if response.last.respond_to?(:body) && data = parse_data(response.last.body)
31
- @response_data = data
32
+ @response_data = if response[1]['Content-Disposition'].to_s.start_with?('attachment')
33
+ '<STREAMED ATTACHMENT FILE>'
34
+ else
35
+ data
36
+ end
32
37
  end
33
38
  @code = response.first
34
39
  end
@@ -38,7 +43,7 @@ module Apipie
38
43
  @verb = request.request_method.to_sym
39
44
  @path = request.path
40
45
  @params = request.request_parameters
41
- if [:POST, :PUT, :PATCH].include?(@verb)
46
+ if [:POST, :PUT, :PATCH, :DELETE].include?(@verb)
42
47
  @request_data = @params
43
48
  else
44
49
  @query = request.query_string
@@ -48,9 +53,9 @@ module Apipie
48
53
  end
49
54
 
50
55
  def parse_data(data)
51
- return nil if data.to_s =~ /^\s*$/
56
+ return nil if data.strip.blank?
52
57
  JSON.parse(data)
53
- rescue StandardError => e
58
+ rescue StandardError
54
59
  data
55
60
  end
56
61
 
@@ -145,12 +150,8 @@ module Apipie
145
150
  end
146
151
 
147
152
  module FunctionalTestRecording
148
- def self.included(base)
149
- base.alias_method_chain :process, :api_recording
150
- end
151
-
152
- def process_with_api_recording(*args) # action, parameters = nil, session = nil, flash = nil, http_method = 'GET')
153
- ret = process_without_api_recording(*args)
153
+ def process(*) # action, parameters = nil, session = nil, flash = nil, http_method = 'GET')
154
+ ret = super
154
155
  if Apipie.configuration.record
155
156
  Apipie::Extractor.call_recorder.analyze_functional_test(self)
156
157
  Apipie::Extractor.call_finished
@@ -159,6 +160,7 @@ module Apipie
159
160
  ensure
160
161
  Apipie::Extractor.clean_call_recorder
161
162
  end
163
+ ruby2_keywords :process if respond_to?(:ruby2_keywords, true)
162
164
  end
163
165
  end
164
166
  end
@@ -3,11 +3,76 @@ require 'set'
3
3
  module Apipie
4
4
  module Extractor
5
5
  class Writer
6
+ class << self
7
+ def compressed
8
+ Apipie.configuration.compress_examples
9
+ end
10
+
11
+ def update_action_description(controller, action)
12
+ updater = ActionDescriptionUpdater.new(controller, action)
13
+ yield updater
14
+ updater.write!
15
+ rescue ActionDescriptionUpdater::ControllerNotFound
16
+ logger.warn("REST_API: Couldn't find controller file for #{controller}")
17
+ rescue ActionDescriptionUpdater::ActionNotFound
18
+ logger.warn("REST_API: Couldn't find action #{action} in #{controller}")
19
+ end
20
+
21
+ def write_recorded_examples(examples)
22
+ FileUtils.mkdir_p(File.dirname(examples_file))
23
+ content = serialize_examples(examples)
24
+ content = Zlib::Deflate.deflate(content).force_encoding('utf-8') if compressed
25
+ File.open(examples_file, 'w') { |f| f << content }
26
+ end
27
+
28
+ def load_recorded_examples
29
+ return {} unless File.exist?(examples_file)
30
+ load_json_examples
31
+ end
32
+
33
+ def examples_file
34
+ pure_path = Rails.root.join(
35
+ Apipie.configuration.doc_path, 'apipie_examples.json'
36
+ )
37
+ zipped_path = pure_path.to_s + '.gz'
38
+ return zipped_path if compressed
39
+ pure_path.to_s
40
+ end
41
+
42
+ protected
43
+
44
+ def serialize_examples(examples)
45
+ JSON.pretty_generate(
46
+ OrderedHash[*examples.sort_by(&:first).flatten(1)]
47
+ )
48
+ end
49
+
50
+ def deserialize_examples(examples_string)
51
+ examples = JSON.parse(examples_string)
52
+ return {} if examples.nil?
53
+ examples.each_value do |records|
54
+ records.each do |record|
55
+ record['verb'] = record['verb'].to_sym if record['verb']
56
+ end
57
+ end
58
+ end
59
+
60
+ def load_json_examples
61
+ raw = IO.read(examples_file)
62
+ raw = Zlib::Inflate.inflate(raw).force_encoding('utf-8') if compressed
63
+ deserialize_examples(raw)
64
+ end
65
+
66
+ def logger
67
+ Extractor.logger
68
+ end
69
+ end
6
70
 
7
71
  def initialize(collector)
8
72
  @collector = collector
9
73
  end
10
74
 
75
+
11
76
  def write_examples
12
77
  merged_examples = merge_old_new_examples
13
78
  self.class.write_recorded_examples(merged_examples)
@@ -26,47 +91,9 @@ module Apipie
26
91
  end
27
92
  end
28
93
 
29
- def self.update_action_description(controller, action)
30
- updater = ActionDescriptionUpdater.new(controller, action)
31
- yield updater
32
- updater.write!
33
- rescue ActionDescriptionUpdater::ControllerNotFound
34
- logger.warn("REST_API: Couldn't find controller file for #{controller}")
35
- rescue ActionDescriptionUpdater::ActionNotFound
36
- logger.warn("REST_API: Couldn't find action #{action} in #{controller}")
37
- end
38
-
39
- def self.write_recorded_examples(examples)
40
- examples_file = self.examples_file
41
- FileUtils.mkdir_p(File.dirname(examples_file))
42
- File.open(examples_file, "w") do |f|
43
- f << JSON.pretty_generate(OrderedHash[*examples.sort_by(&:first).flatten(1)])
44
- end
45
- end
46
-
47
- def self.load_recorded_examples
48
- examples_file = self.examples_file
49
- if File.exists?(examples_file)
50
- return load_json_examples
51
- end
52
- return {}
53
- end
54
-
55
- def self.examples_file
56
- File.join(Rails.root,Apipie.configuration.doc_path,"apipie_examples.json")
57
- end
58
94
 
59
95
  protected
60
96
 
61
- def self.load_json_examples
62
- examples = JSON.load(IO.read(examples_file))
63
- return {} if examples.nil?
64
- examples.each do |method, records|
65
- records.each do |record|
66
- record["verb"] = record["verb"].to_sym if record["verb"]
67
- end
68
- end
69
- end
70
97
 
71
98
  def desc_to_s(description)
72
99
  "#{description[:controller].name}##{description[:action]}"
@@ -78,21 +105,26 @@ module Apipie
78
105
  %w[title verb path versions query request_data response_data code show_in_doc recorded].each do |k|
79
106
  next unless call.has_key?(k)
80
107
  ordered_call[k] = case call[k]
81
- when ActiveSupport::HashWithIndifferentAccess
82
- # UploadedFiles break the to_json call, turn them into a string so they don't break
83
- call[k].each do |pkey, pval|
84
- if (pval.is_a?(Rack::Test::UploadedFile) || pval.is_a?(ActionDispatch::Http::UploadedFile))
85
- call[k][pkey] = "<FILE CONTENT '#{pval.original_filename}'>"
86
- end
87
- end
88
- JSON.parse(call[k].to_json) # to_hash doesn't work recursively and I'm too lazy to write the recursion:)
89
- else
90
- call[k]
91
- end
92
- end
108
+ when ActiveSupport::HashWithIndifferentAccess
109
+ convert_file_value(call[k]).to_hash
110
+ else
111
+ call[k]
112
+ end
113
+ end
93
114
  return ordered_call
94
115
  end
95
116
 
117
+ def convert_file_value hash
118
+ hash.each do |k, v|
119
+ if v.is_a?(Rack::Test::UploadedFile) || v.is_a?(ActionDispatch::Http::UploadedFile)
120
+ hash[k] = "<FILE CONTENT '#{v.original_filename}'>"
121
+ elsif v.is_a?(Hash)
122
+ hash[k] = convert_file_value(v)
123
+ end
124
+ end
125
+ hash
126
+ end
127
+
96
128
  def load_recorded_examples
97
129
  self.class.load_recorded_examples
98
130
  end
@@ -131,7 +163,7 @@ module Apipie
131
163
  new_example[:title] ||= old_example["title"] if old_example["title"].present?
132
164
  end
133
165
  new_example
134
- end
166
+ end
135
167
  end
136
168
 
137
169
  def load_new_examples
@@ -158,7 +190,7 @@ module Apipie
158
190
  end
159
191
 
160
192
  def load_old_examples
161
- if File.exists?(@examples_file)
193
+ if File.exist?(@examples_file)
162
194
  if defined? SafeYAML
163
195
  return YAML.load_file(@examples_file, :safe=>false)
164
196
  else
@@ -172,10 +204,6 @@ module Apipie
172
204
  self.class.logger
173
205
  end
174
206
 
175
- def self.logger
176
- Extractor.logger
177
- end
178
-
179
207
  def showable_in_doc?(call)
180
208
  # we don't want to mess documentation with too large examples
181
209
  if hash_nodes_count(call["request_data"]) + hash_nodes_count(call["response_data"]) > 100
@@ -267,7 +295,7 @@ module Apipie
267
295
  end
268
296
 
269
297
  def controller_content
270
- raise ControllerNotFound.new unless controller_path && File.exists?(controller_path)
298
+ raise ControllerNotFound.new unless controller_path && File.exist?(controller_path)
271
299
  @controller_content ||= File.read(controller_path)
272
300
  end
273
301
 
@@ -295,7 +323,7 @@ module Apipie
295
323
  desc ||= case @action.to_s
296
324
  when "show", "create", "update", "destroy"
297
325
  name = name.singularize
298
- "#{@action.capitalize} #{name =~ /^[aeiou]/ ? "an" : "a"} #{name}"
326
+ "#{@action.capitalize} #{name =~ /^[aeiou]/ ? 'an' : 'a'} #{name}"
299
327
  when "index"
300
328
  "List #{name}"
301
329
  end