apipie-rails 0.3.6 → 0.5.17

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +23 -7
  3. data/CHANGELOG.md +147 -2
  4. data/Gemfile +1 -0
  5. data/Gemfile.rails41 +2 -0
  6. data/Gemfile.rails42 +10 -1
  7. data/Gemfile.rails50 +9 -0
  8. data/Gemfile.rails51 +9 -0
  9. data/Gemfile.rails60 +14 -0
  10. data/PROPOSAL_FOR_RESPONSE_DESCRIPTIONS.md +244 -0
  11. data/README.rst +570 -17
  12. data/apipie-rails.gemspec +3 -3
  13. data/app/controllers/apipie/apipies_controller.rb +48 -17
  14. data/app/views/apipie/apipies/_method_detail.erb +21 -0
  15. data/app/views/apipie/apipies/_params.html.erb +4 -2
  16. data/app/views/apipie/apipies/index.html.erb +5 -1
  17. data/app/views/apipie/apipies/resource.html.erb +3 -0
  18. data/app/views/layouts/apipie/apipie.html.erb +1 -1
  19. data/config/locales/en.yml +1 -0
  20. data/config/locales/fr.yml +31 -0
  21. data/config/locales/it.yml +31 -0
  22. data/config/locales/ja.yml +31 -0
  23. data/lib/apipie/apipie_module.rb +22 -4
  24. data/lib/apipie/application.rb +55 -28
  25. data/lib/apipie/configuration.rb +19 -3
  26. data/lib/apipie/core_ext/route.rb +9 -0
  27. data/lib/apipie/dsl_definition.rb +151 -10
  28. data/lib/apipie/error_description.rb +9 -2
  29. data/lib/apipie/errors.rb +34 -0
  30. data/lib/apipie/extractor/collector.rb +4 -0
  31. data/lib/apipie/extractor/recorder.rb +13 -12
  32. data/lib/apipie/extractor/writer.rb +83 -55
  33. data/lib/apipie/extractor.rb +10 -4
  34. data/lib/apipie/method_description.rb +51 -4
  35. data/lib/apipie/param_description.rb +56 -2
  36. data/lib/apipie/resource_description.rb +10 -3
  37. data/lib/apipie/response_description.rb +131 -0
  38. data/lib/apipie/response_description_adapter.rb +200 -0
  39. data/lib/apipie/routes_formatter.rb +1 -1
  40. data/lib/apipie/rspec/response_validation_helper.rb +194 -0
  41. data/lib/apipie/static_dispatcher.rb +3 -2
  42. data/lib/apipie/swagger_generator.rb +708 -0
  43. data/lib/apipie/tag_list_description.rb +11 -0
  44. data/lib/apipie/validator.rb +69 -8
  45. data/lib/apipie/version.rb +1 -1
  46. data/lib/apipie-rails.rb +7 -0
  47. data/lib/tasks/apipie.rake +103 -8
  48. data/spec/controllers/apipies_controller_spec.rb +52 -12
  49. data/spec/controllers/concerns_controller_spec.rb +2 -2
  50. data/spec/controllers/extended_controller_spec.rb +14 -0
  51. data/spec/controllers/memes_controller_spec.rb +10 -0
  52. data/spec/controllers/users_controller_spec.rb +115 -75
  53. data/spec/dummy/app/controllers/application_controller.rb +5 -1
  54. data/spec/dummy/app/controllers/concerns/extending_concern.rb +12 -0
  55. data/spec/dummy/app/controllers/concerns/sample_controller.rb +5 -5
  56. data/spec/dummy/app/controllers/extended_controller.rb +14 -0
  57. data/spec/dummy/app/controllers/pets_controller.rb +408 -0
  58. data/spec/dummy/app/controllers/pets_using_auto_views_controller.rb +73 -0
  59. data/spec/dummy/app/controllers/pets_using_self_describing_classes_controller.rb +95 -0
  60. data/spec/dummy/app/controllers/tagged_cats_controller.rb +32 -0
  61. data/spec/dummy/app/controllers/tagged_dogs_controller.rb +15 -0
  62. data/spec/dummy/app/controllers/twitter_example_controller.rb +5 -0
  63. data/spec/dummy/app/controllers/users_controller.rb +19 -11
  64. data/spec/dummy/components/test_engine/Gemfile +6 -0
  65. data/spec/dummy/components/test_engine/app/controllers/test_engine/application_controller.rb +4 -0
  66. data/spec/dummy/components/test_engine/app/controllers/test_engine/memes_controller.rb +37 -0
  67. data/spec/dummy/components/test_engine/config/routes.rb +3 -0
  68. data/spec/dummy/components/test_engine/db/.gitkeep +0 -0
  69. data/spec/dummy/components/test_engine/lib/test_engine.rb +7 -0
  70. data/spec/dummy/components/test_engine/test_engine.gemspec +11 -0
  71. data/spec/dummy/config/application.rb +5 -0
  72. data/spec/dummy/config/environments/development.rb +3 -0
  73. data/spec/dummy/config/environments/production.rb +3 -0
  74. data/spec/dummy/config/environments/test.rb +3 -0
  75. data/spec/dummy/config/initializers/apipie.rb +3 -1
  76. data/spec/dummy/config/routes.rb +24 -1
  77. data/spec/lib/extractor/writer_spec.rb +32 -4
  78. data/spec/lib/file_handler_spec.rb +18 -0
  79. data/spec/lib/method_description_spec.rb +34 -0
  80. data/spec/lib/swagger/openapi_2_0_schema.json +1607 -0
  81. data/spec/lib/swagger/rake_swagger_spec.rb +139 -0
  82. data/spec/lib/swagger/response_validation_spec.rb +104 -0
  83. data/spec/lib/swagger/swagger_dsl_spec.rb +658 -0
  84. data/spec/lib/validator_spec.rb +58 -0
  85. data/spec/lib/validators/array_validator_spec.rb +28 -8
  86. data/spec/spec_helper.rb +68 -0
  87. metadata +75 -23
  88. data/Gemfile +0 -7
  89. data/Gemfile.rails32 +0 -6
  90. data/Gemfile.rails40 +0 -5
  91. 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
@@ -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,7 +260,7 @@ 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
265
  raise UnknownParam.new(param) if method_params.select {|_,p| p.name.to_s == param.to_s}.empty?
243
266
  end
@@ -295,6 +318,7 @@ module Apipie
295
318
  end
296
319
  end
297
320
 
321
+
298
322
  # this describes the params, it's in separate module because it's
299
323
  # used in Validators as well
300
324
  module Param
@@ -315,6 +339,13 @@ module Apipie
315
339
  block]
316
340
  end
317
341
 
342
+ def property(param_name, validator, desc_or_options = nil, options = {}, &block) #:doc:
343
+ return unless Apipie.active_dsl?
344
+ options[:only_in] ||= :response
345
+ options[:required] = true if options[:required].nil?
346
+ param(param_name, validator, desc_or_options, options, &block)
347
+ end
348
+
318
349
  # Reuses param group for this method. The definition is looked up
319
350
  # in scope of this controller. If the group was defined in
320
351
  # different controller, the second param can be used to specify it.
@@ -340,6 +371,65 @@ module Apipie
340
371
  @_current_param_group = nil
341
372
  end
342
373
 
374
+ # Describe possible responses
375
+ #
376
+ # Example:
377
+ # def_param_group :user do
378
+ # param :user, Hash do
379
+ # param :name, String
380
+ # end
381
+ # end
382
+ #
383
+ # returns :user, "the speaker"
384
+ # returns "the speaker" do
385
+ # param_group: :user
386
+ # end
387
+ # returns :param_group => :user, "the speaker"
388
+ # returns :user, :code => 201, :desc => "the created speaker record"
389
+ # returns :array_of => :user, "many speakers"
390
+ # def hello_world
391
+ # render json: {user: {name: "Alfred"}}
392
+ # end
393
+ #
394
+ def returns(pgroup_or_options, desc_or_options=nil, options={}, &block) #:doc:
395
+ return unless Apipie.active_dsl?
396
+
397
+
398
+ if desc_or_options.is_a? Hash
399
+ options.merge!(desc_or_options)
400
+ elsif !desc_or_options.nil?
401
+ options[:desc] = desc_or_options
402
+ end
403
+
404
+ if pgroup_or_options.is_a? Hash
405
+ options.merge!(pgroup_or_options)
406
+ else
407
+ options[:param_group] = pgroup_or_options
408
+ end
409
+
410
+ code = options[:code] || 200
411
+ scope = options[:scope] || _default_param_group_scope
412
+ descriptor = options[:param_group] || options[:array_of]
413
+
414
+ if block.nil?
415
+ if descriptor.is_a? ResponseDescriptionAdapter
416
+ adapter = descriptor
417
+ elsif descriptor.respond_to? :describe_own_properties
418
+ adapter = ResponseDescriptionAdapter.from_self_describing_class(descriptor)
419
+ else
420
+ begin
421
+ block = Apipie.get_param_group(scope, descriptor) if descriptor
422
+ rescue
423
+ raise "No param_group or self-describing class named #{descriptor}"
424
+ end
425
+ end
426
+ elsif descriptor
427
+ raise "cannot specify both block and param_group"
428
+ end
429
+
430
+ _apipie_dsl_data[:returns][code] = [options, scope, block, adapter]
431
+ end
432
+
343
433
  # where the group definition should be looked up when no scope
344
434
  # given. This is expected to return a controller.
345
435
  def _default_param_group_scope
@@ -381,6 +471,51 @@ module Apipie
381
471
  _apipie_concern_subst.merge!(subst_hash)
382
472
  end
383
473
 
474
+ # Allows to update existing params after definition was made (usually needed
475
+ # when extending the API form plugins).
476
+ #
477
+ # UsersController.apipie_update_params([:create, :update]) do
478
+ # param :user, Hash do
479
+ # param :oauth, String
480
+ # end
481
+ # end
482
+ #
483
+ # The block is evaluated in scope of the controller. Ohe can pass some additional
484
+ # objects via additional arguments like this:
485
+ #
486
+ # UsersController.apipie_update_params([:create, :update], [:name, :secret]) do |param_names|
487
+ # param :user, Hash do
488
+ # param_names.each { |p| param p, String }
489
+ # end
490
+ # end
491
+ def _apipie_update_params(method_desc, dsl_data)
492
+ params_ordered = dsl_data[:params].map do |args|
493
+ Apipie::ParamDescription.from_dsl_data(method_desc, args)
494
+ end
495
+ ParamDescription.merge(method_desc.params_ordered_self, params_ordered)
496
+ end
497
+
498
+ def _apipie_update_meta(method_desc, dsl_data)
499
+ return unless dsl_data[:meta] && dsl_data[:meta].is_a?(Hash)
500
+
501
+ method_desc.metadata ||= {}
502
+ method_desc.metadata.merge!(dsl_data[:meta])
503
+ end
504
+
505
+ def apipie_update_methods(methods, *args, &block)
506
+ methods.each do |method|
507
+ method_desc = Apipie.get_method_description(self, method)
508
+ unless method_desc
509
+ raise "Could not find method description for #{self}##{method}. Was the method defined?"
510
+ end
511
+ dsl_data = _apipie_eval_dsl(*args, &block)
512
+ _apipie_update_params(method_desc, dsl_data)
513
+ _apipie_update_meta(method_desc, dsl_data)
514
+ end
515
+ end
516
+ # backwards compatibility
517
+ alias_method :apipie_update_params, :apipie_update_methods
518
+
384
519
  def _apipie_concern_subst
385
520
  @_apipie_concern_subst ||= {:controller_path => self.controller_path,
386
521
  :resource_id => Apipie.get_resource_name(self)}
@@ -428,12 +563,19 @@ module Apipie
428
563
  description = Apipie.define_method_description(controller, method_name, _apipie_dsl_data)
429
564
  controller._apipie_define_validators(description)
430
565
  end
566
+ _apipie_concern_update_api_blocks.each do |(methods, block)|
567
+ controller.apipie_update_methods(methods, &block)
568
+ end
431
569
  end
432
570
 
433
571
  def _apipie_concern_data
434
572
  @_apipie_concern_data ||= []
435
573
  end
436
574
 
575
+ def _apipie_concern_update_api_blocks
576
+ @_apipie_concern_update_api_blocks ||= []
577
+ end
578
+
437
579
  def apipie_concern?
438
580
  true
439
581
  end
@@ -449,6 +591,10 @@ module Apipie
449
591
  _apipie_dsl_data_clear
450
592
  end
451
593
 
594
+ def update_api(*methods, &block)
595
+ _apipie_concern_update_api_blocks << [methods, block]
596
+ end
597
+
452
598
  end
453
599
 
454
600
  class ResourceDescriptionDsl
@@ -461,14 +607,9 @@ module Apipie
461
607
  @controller = controller
462
608
  end
463
609
 
464
- def _eval_dsl(&block)
465
- instance_eval(&block)
466
- return _apipie_dsl_data
467
- end
468
-
469
610
  # evaluates resource description DSL and returns results
470
611
  def self.eval_dsl(controller, &block)
471
- dsl_data = self.new(controller)._eval_dsl(&block)
612
+ dsl_data = self.new(controller)._apipie_eval_dsl(&block)
472
613
  if dsl_data[:api_versions].empty?
473
614
  dsl_data[:api_versions] = Apipie.controller_versions(controller)
474
615
  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(*args) # action, parameters = nil, session = nil, flash = nil, http_method = 'GET')
154
+ ret = super(*args)
154
155
  if Apipie.configuration.record
155
156
  Apipie::Extractor.call_recorder.analyze_functional_test(self)
156
157
  Apipie::Extractor.call_finished
@@ -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
@@ -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
@@ -9,15 +9,21 @@ require 'apipie/extractor/collector'
9
9
  class Apipie::Railtie
10
10
  initializer 'apipie.extractor' do |app|
11
11
  ActiveSupport.on_load :action_controller do
12
- before_filter do |controller|
12
+ before_action do |controller|
13
13
  if Apipie.configuration.record
14
14
  Apipie::Extractor.call_recorder.analyse_controller(controller)
15
15
  end
16
16
  end
17
17
  end
18
18
  app.middleware.use ::Apipie::Extractor::Recorder::Middleware
19
- ActionController::TestCase::Behavior.instance_eval do
20
- include Apipie::Extractor::Recorder::FunctionalTestRecording
19
+
20
+ if Gem::Version.new(Rails.version) < Gem::Version.new('5.0.0')
21
+ ActionController::TestCase::Behavior.instance_eval do
22
+ prepend Apipie::Extractor::Recorder::FunctionalTestRecording
23
+ end
24
+ else
25
+ ActionController::TestCase.send(:prepend, Apipie::Extractor::Recorder::FunctionalTestRecording)
26
+ ActionController::TestCase::Behavior.send(:prepend, Apipie::Extractor::Recorder::FunctionalTestRecording)
21
27
  end
22
28
  end
23
29
  end
@@ -154,7 +160,7 @@ module Apipie
154
160
  def update_api_descriptions
155
161
  apis_from_docs = all_apis_from_docs
156
162
  @apis_from_routes.each do |(controller, action), new_apis|
157
- method_key = "#{Apipie.get_resource_name(controller.constantize)}##{action}"
163
+ method_key = "#{Apipie.get_resource_name(controller.safe_constantize || next)}##{action}"
158
164
  old_apis = apis_from_docs[method_key] || []
159
165
  new_apis.each do |new_api|
160
166
  new_api[:path].sub!(/\(\.:format\)$/,"") if new_api[:path]