restapi 0.0.4 → 0.0.5
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.
- data/Gemfile +0 -1
 - data/Gemfile.lock +0 -10
 - data/README.rdoc +18 -12
 - data/app/controllers/restapi/restapis_controller.rb +28 -1
 - data/app/views/layouts/restapi/restapi.html.erb +1 -0
 - data/app/views/restapi/restapis/_params.html.erb +22 -0
 - data/app/views/restapi/restapis/_params_plain.html.erb +16 -0
 - data/app/views/restapi/restapis/index.html.erb +5 -5
 - data/app/views/restapi/restapis/method.html.erb +8 -4
 - data/app/views/restapi/restapis/plain.html.erb +70 -0
 - data/app/views/restapi/restapis/resource.html.erb +16 -5
 - data/app/views/restapi/restapis/static.html.erb +4 -6
 - data/lib/restapi.rb +2 -1
 - data/lib/restapi/application.rb +72 -22
 - data/lib/restapi/client/generator.rb +104 -0
 - data/lib/restapi/client/template/Gemfile.tt +5 -0
 - data/lib/restapi/client/template/README.tt +3 -0
 - data/lib/restapi/client/template/base.rb.tt +33 -0
 - data/lib/restapi/client/template/bin.rb.tt +110 -0
 - data/lib/restapi/client/template/cli.rb.tt +25 -0
 - data/lib/restapi/client/template/cli_command.rb.tt +129 -0
 - data/lib/restapi/client/template/client.rb.tt +10 -0
 - data/lib/restapi/client/template/resource.rb.tt +17 -0
 - data/lib/restapi/dsl_definition.rb +20 -2
 - data/lib/restapi/error_description.rb +8 -2
 - data/lib/restapi/extractor.rb +143 -0
 - data/lib/restapi/extractor/collector.rb +113 -0
 - data/lib/restapi/extractor/recorder.rb +122 -0
 - data/lib/restapi/extractor/writer.rb +356 -0
 - data/lib/restapi/helpers.rb +10 -5
 - data/lib/restapi/markup.rb +12 -12
 - data/lib/restapi/method_description.rb +52 -8
 - data/lib/restapi/param_description.rb +6 -5
 - data/lib/restapi/railtie.rb +1 -1
 - data/lib/restapi/resource_description.rb +1 -1
 - data/lib/restapi/restapi_module.rb +43 -0
 - data/lib/restapi/validator.rb +70 -3
 - data/lib/restapi/version.rb +1 -1
 - data/lib/tasks/restapi.rake +120 -121
 - data/restapi.gemspec +0 -2
 - data/spec/controllers/restapis_controller_spec.rb +41 -6
 - data/spec/controllers/users_controller_spec.rb +51 -12
 - data/spec/dummy/app/controllers/application_controller.rb +0 -2
 - data/spec/dummy/app/controllers/twitter_example_controller.rb +4 -9
 - data/spec/dummy/app/controllers/users_controller.rb +13 -6
 - data/spec/dummy/config/initializers/restapi.rb +7 -0
 - data/spec/dummy/doc/restapi_examples.yml +28 -0
 - metadata +49 -76
 - data/app/helpers/restapi/restapis_helper.rb +0 -31
 
| 
         @@ -16,6 +16,7 @@ module Restapi 
     | 
|
| 
       16 
16 
     | 
    
         
             
                #   Long description...
         
     | 
| 
       17 
17 
     | 
    
         
             
                # EOS
         
     | 
| 
       18 
18 
     | 
    
         
             
                def resource_description(options = {}, &block) #:doc:
         
     | 
| 
      
 19 
     | 
    
         
            +
                  return unless Restapi.active_dsl?
         
     | 
| 
       19 
20 
     | 
    
         
             
                  Restapi.remove_resource_description(self)
         
     | 
| 
       20 
21 
     | 
    
         
             
                  Restapi.define_resource_description(self, &block) if block_given?
         
     | 
| 
       21 
22 
     | 
    
         
             
                end
         
     | 
| 
         @@ -26,6 +27,7 @@ module Restapi 
     | 
|
| 
       26 
27 
     | 
    
         
             
                #   api :GET, "/resource_route", "short description",
         
     | 
| 
       27 
28 
     | 
    
         
             
                #
         
     | 
| 
       28 
29 
     | 
    
         
             
                def api(method, path, desc = nil) #:doc:
         
     | 
| 
      
 30 
     | 
    
         
            +
                  return unless Restapi.active_dsl?
         
     | 
| 
       29 
31 
     | 
    
         
             
                  Restapi.add_method_description_args(method, path, desc)
         
     | 
| 
       30 
32 
     | 
    
         
             
                end
         
     | 
| 
       31 
33 
     | 
    
         | 
| 
         @@ -38,6 +40,7 @@ module Restapi 
     | 
|
| 
       38 
40 
     | 
    
         
             
                #   end
         
     | 
| 
       39 
41 
     | 
    
         
             
                #
         
     | 
| 
       40 
42 
     | 
    
         
             
                def desc(description) #:doc:
         
     | 
| 
      
 43 
     | 
    
         
            +
                  return unless Restapi.active_dsl?
         
     | 
| 
       41 
44 
     | 
    
         
             
                  if Restapi.last_description
         
     | 
| 
       42 
45 
     | 
    
         
             
                    raise "Double method description."
         
     | 
| 
       43 
46 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -45,9 +48,21 @@ module Restapi 
     | 
|
| 
       45 
48 
     | 
    
         
             
                end
         
     | 
| 
       46 
49 
     | 
    
         
             
                alias :description :desc
         
     | 
| 
       47 
50 
     | 
    
         | 
| 
      
 51 
     | 
    
         
            +
                # Reference other similar method
         
     | 
| 
      
 52 
     | 
    
         
            +
                #
         
     | 
| 
      
 53 
     | 
    
         
            +
                #   api :PUT, '/articles/:id'
         
     | 
| 
      
 54 
     | 
    
         
            +
                #   see "articles#create"
         
     | 
| 
      
 55 
     | 
    
         
            +
                #   def update; end
         
     | 
| 
      
 56 
     | 
    
         
            +
                def see(method_key)
         
     | 
| 
      
 57 
     | 
    
         
            +
                  return unless Restapi.active_dsl?
         
     | 
| 
      
 58 
     | 
    
         
            +
                  raise "'See' method called twice." if Restapi.last_see
         
     | 
| 
      
 59 
     | 
    
         
            +
                  Restapi.last_see = method_key
         
     | 
| 
      
 60 
     | 
    
         
            +
                end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
       48 
62 
     | 
    
         
             
                # Show some example of what does the described
         
     | 
| 
       49 
63 
     | 
    
         
             
                # method return.
         
     | 
| 
       50 
64 
     | 
    
         
             
                def example(example) #:doc:
         
     | 
| 
      
 65 
     | 
    
         
            +
                  return unless Restapi.active_dsl?
         
     | 
| 
       51 
66 
     | 
    
         
             
                  Restapi.add_example(example)
         
     | 
| 
       52 
67 
     | 
    
         
             
                end
         
     | 
| 
       53 
68 
     | 
    
         | 
| 
         @@ -55,12 +70,14 @@ module Restapi 
     | 
|
| 
       55 
70 
     | 
    
         
             
                #
         
     | 
| 
       56 
71 
     | 
    
         
             
                # Example:
         
     | 
| 
       57 
72 
     | 
    
         
             
                #   error :desc => "speaker is sleeping", :code => 500
         
     | 
| 
      
 73 
     | 
    
         
            +
                #   error 500, "speaker is sleeping"
         
     | 
| 
       58 
74 
     | 
    
         
             
                #   def hello_world
         
     | 
| 
       59 
75 
     | 
    
         
             
                #     return 500 if self.speaker.sleeping?
         
     | 
| 
       60 
76 
     | 
    
         
             
                #     puts "hello world"
         
     | 
| 
       61 
77 
     | 
    
         
             
                #   end
         
     | 
| 
       62 
78 
     | 
    
         
             
                #
         
     | 
| 
       63 
     | 
    
         
            -
                def error(args) #:doc:
         
     | 
| 
      
 79 
     | 
    
         
            +
                def error(*args) #:doc:
         
     | 
| 
      
 80 
     | 
    
         
            +
                  return unless Restapi.active_dsl?
         
     | 
| 
       64 
81 
     | 
    
         
             
                  Restapi.last_errors << Restapi::ErrorDescription.new(args)
         
     | 
| 
       65 
82 
     | 
    
         
             
                end
         
     | 
| 
       66 
83 
     | 
    
         | 
| 
         @@ -73,14 +90,15 @@ module Restapi 
     | 
|
| 
       73 
90 
     | 
    
         
             
                #   end
         
     | 
| 
       74 
91 
     | 
    
         
             
                #
         
     | 
| 
       75 
92 
     | 
    
         
             
                def param(param_name, *args, &block) #:doc:
         
     | 
| 
      
 93 
     | 
    
         
            +
                  return unless Restapi.active_dsl?
         
     | 
| 
       76 
94 
     | 
    
         
             
                  Restapi.last_params << Restapi::ParamDescription.new(param_name, *args, &block)
         
     | 
| 
       77 
95 
     | 
    
         
             
                end
         
     | 
| 
       78 
96 
     | 
    
         | 
| 
       79 
97 
     | 
    
         
             
                # create method api and redefine newly added method
         
     | 
| 
       80 
98 
     | 
    
         
             
                def method_added(method_name) #:doc:
         
     | 
| 
       81 
     | 
    
         
            -
             
     | 
| 
       82 
99 
     | 
    
         
             
                  super
         
     | 
| 
       83 
100 
     | 
    
         | 
| 
      
 101 
     | 
    
         
            +
                  return unless Restapi.active_dsl?
         
     | 
| 
       84 
102 
     | 
    
         
             
                  return unless Restapi.restapi_provided?
         
     | 
| 
       85 
103 
     | 
    
         | 
| 
       86 
104 
     | 
    
         
             
                  # remove method description if exists and create new one
         
     | 
| 
         @@ -3,9 +3,15 @@ module Restapi 
     | 
|
| 
       3 
3 
     | 
    
         
             
              class ErrorDescription
         
     | 
| 
       4 
4 
     | 
    
         | 
| 
       5 
5 
     | 
    
         
             
                attr_reader :code, :description
         
     | 
| 
       6 
     | 
    
         
            -
             
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
       7 
7 
     | 
    
         
             
                def initialize(args)
         
     | 
| 
       8 
     | 
    
         
            -
                  args  
     | 
| 
      
 8 
     | 
    
         
            +
                  if args.first.is_a? Hash
         
     | 
| 
      
 9 
     | 
    
         
            +
                    args = args.first
         
     | 
| 
      
 10 
     | 
    
         
            +
                  elsif args.count == 2
         
     | 
| 
      
 11 
     | 
    
         
            +
                    args = {:code => args.first, :description => args.second}
         
     | 
| 
      
 12 
     | 
    
         
            +
                  else
         
     | 
| 
      
 13 
     | 
    
         
            +
                    raise ArgumentError "RestapiError: Bad use of error method."
         
     | 
| 
      
 14 
     | 
    
         
            +
                  end
         
     | 
| 
       9 
15 
     | 
    
         
             
                  @code = args[:code] || args['code']
         
     | 
| 
       10 
16 
     | 
    
         
             
                  @description = args[:desc] || args[:description] || args['desc'] || args['description']
         
     | 
| 
       11 
17 
     | 
    
         
             
                end
         
     | 
| 
         @@ -0,0 +1,143 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'singleton'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'fileutils'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'set'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'yaml'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require 'restapi/extractor/recorder'
         
     | 
| 
      
 6 
     | 
    
         
            +
            require 'restapi/extractor/writer'
         
     | 
| 
      
 7 
     | 
    
         
            +
            require 'restapi/extractor/collector'
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            class Restapi::Railtie
         
     | 
| 
      
 10 
     | 
    
         
            +
              if ENV["RESTAPI_RECORD"]
         
     | 
| 
      
 11 
     | 
    
         
            +
                initializer 'restapi.extractor' do |app|
         
     | 
| 
      
 12 
     | 
    
         
            +
                  ActiveSupport.on_load :action_controller do
         
     | 
| 
      
 13 
     | 
    
         
            +
                    before_filter do |controller|
         
     | 
| 
      
 14 
     | 
    
         
            +
                      Restapi::Extractor.call_recorder.analyse_controller(controller)
         
     | 
| 
      
 15 
     | 
    
         
            +
                    end
         
     | 
| 
      
 16 
     | 
    
         
            +
                  end
         
     | 
| 
      
 17 
     | 
    
         
            +
                  app.middleware.use ::Restapi::Extractor::Recorder::Middleware
         
     | 
| 
      
 18 
     | 
    
         
            +
                  ActionController::TestCase::Behavior.instance_eval do
         
     | 
| 
      
 19 
     | 
    
         
            +
                    include Restapi::Extractor::Recorder::FunctionalTestRecording
         
     | 
| 
      
 20 
     | 
    
         
            +
                  end
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
              end
         
     | 
| 
      
 23 
     | 
    
         
            +
            end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
            module Restapi
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
              module Extractor
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                class << self
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                  def logger
         
     | 
| 
      
 32 
     | 
    
         
            +
                    Rails.logger
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                  def call_recorder
         
     | 
| 
      
 36 
     | 
    
         
            +
                    Thread.current[:restapi_call_recorder] ||= Recorder.new
         
     | 
| 
      
 37 
     | 
    
         
            +
                  end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                  def call_finished
         
     | 
| 
      
 40 
     | 
    
         
            +
                    @collector ||= Collector.new
         
     | 
| 
      
 41 
     | 
    
         
            +
                    if record = call_recorder.record
         
     | 
| 
      
 42 
     | 
    
         
            +
                      @collector.handle_record(record)
         
     | 
| 
      
 43 
     | 
    
         
            +
                    end
         
     | 
| 
      
 44 
     | 
    
         
            +
                  ensure
         
     | 
| 
      
 45 
     | 
    
         
            +
                    Thread.current[:restapi_call_recorder] = nil
         
     | 
| 
      
 46 
     | 
    
         
            +
                  end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                  def write_docs
         
     | 
| 
      
 49 
     | 
    
         
            +
                    Writer.new(@collector).write_docs if @collector
         
     | 
| 
      
 50 
     | 
    
         
            +
                  end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                  def write_examples
         
     | 
| 
      
 53 
     | 
    
         
            +
                    Writer.new(@collector).write_examples if @collector
         
     | 
| 
      
 54 
     | 
    
         
            +
                  end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                  def apis_from_routes
         
     | 
| 
      
 57 
     | 
    
         
            +
                    return @apis_from_routes if @apis_from_routes
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                    api_prefix = Restapi.configuration.api_base_url.sub(/\/$/,"")
         
     | 
| 
      
 60 
     | 
    
         
            +
                    all_routes = Rails.application.routes.routes.map do |r|
         
     | 
| 
      
 61 
     | 
    
         
            +
                      {
         
     | 
| 
      
 62 
     | 
    
         
            +
                        :verb => case r.verb
         
     | 
| 
      
 63 
     | 
    
         
            +
                                 when Regexp then r.verb.source[/\w+/]
         
     | 
| 
      
 64 
     | 
    
         
            +
                                 else r.verb.to_s
         
     | 
| 
      
 65 
     | 
    
         
            +
                                 end,
         
     | 
| 
      
 66 
     | 
    
         
            +
                        :path => case
         
     | 
| 
      
 67 
     | 
    
         
            +
                                 when r.path.respond_to?(:spec) then r.path.spec.to_s
         
     | 
| 
      
 68 
     | 
    
         
            +
                                 else r.path.to_s
         
     | 
| 
      
 69 
     | 
    
         
            +
                                 end,
         
     | 
| 
      
 70 
     | 
    
         
            +
                        :controller => r.requirements[:controller],
         
     | 
| 
      
 71 
     | 
    
         
            +
                        :action => r.requirements[:action]
         
     | 
| 
      
 72 
     | 
    
         
            +
                      }
         
     | 
| 
      
 73 
     | 
    
         
            +
                      
         
     | 
| 
      
 74 
     | 
    
         
            +
                      
         
     | 
| 
      
 75 
     | 
    
         
            +
                    end
         
     | 
| 
      
 76 
     | 
    
         
            +
                    api_routes = all_routes.find_all do |r|
         
     | 
| 
      
 77 
     | 
    
         
            +
                      r[:path].starts_with?(Restapi.configuration.api_base_url)
         
     | 
| 
      
 78 
     | 
    
         
            +
                    end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                    @apis_from_routes = Hash.new { |h, k| h[k] = [] }
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                    api_routes.each do |route|
         
     | 
| 
      
 83 
     | 
    
         
            +
                      controller_path, action = route[:controller], route[:action]
         
     | 
| 
      
 84 
     | 
    
         
            +
                      next unless controller_path && action
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
                      controller = "#{controller_path}_controller".camelize
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                      path = if /^#{Regexp.escape(api_prefix)}(.*)$/ =~ route[:path]
         
     | 
| 
      
 89 
     | 
    
         
            +
                               $1.sub!(/\(\.:format\)$/,"")
         
     | 
| 
      
 90 
     | 
    
         
            +
                             else
         
     | 
| 
      
 91 
     | 
    
         
            +
                               nil
         
     | 
| 
      
 92 
     | 
    
         
            +
                             end
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
                      if route[:verb].present?
         
     | 
| 
      
 95 
     | 
    
         
            +
                        @apis_from_routes[[controller, action]] << {:method => route[:verb], :path => path}
         
     | 
| 
      
 96 
     | 
    
         
            +
                      end
         
     | 
| 
      
 97 
     | 
    
         
            +
                    end
         
     | 
| 
      
 98 
     | 
    
         
            +
                    @apis_from_routes
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
                    apis_from_docs = Restapi.method_descriptions.reduce({}) do |h, (method, desc)|
         
     | 
| 
      
 101 
     | 
    
         
            +
                      apis = desc.apis.map do |api|
         
     | 
| 
      
 102 
     | 
    
         
            +
                        {:method => api.http_method, :path => api.api_url, :desc => api.short_description}
         
     | 
| 
      
 103 
     | 
    
         
            +
                      end
         
     | 
| 
      
 104 
     | 
    
         
            +
                      h.update(method => apis)
         
     | 
| 
      
 105 
     | 
    
         
            +
                    end
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
                    @apis_from_routes.each do |(controller, action), new_apis|
         
     | 
| 
      
 108 
     | 
    
         
            +
                      method_key = "#{controller.constantize.controller_name}##{action}"
         
     | 
| 
      
 109 
     | 
    
         
            +
                      old_apis = apis_from_docs[method_key] || []
         
     | 
| 
      
 110 
     | 
    
         
            +
                      new_apis.each do |new_api|
         
     | 
| 
      
 111 
     | 
    
         
            +
                        new_api[:path].sub!(/\(\.:format\)$/,"")
         
     | 
| 
      
 112 
     | 
    
         
            +
                        if old_api = old_apis.find { |api| api[:path] == "#{api_prefix}#{new_api[:path]}" }
         
     | 
| 
      
 113 
     | 
    
         
            +
                          new_api[:desc] = old_api[:desc]
         
     | 
| 
      
 114 
     | 
    
         
            +
                        end
         
     | 
| 
      
 115 
     | 
    
         
            +
                      end
         
     | 
| 
      
 116 
     | 
    
         
            +
                    end
         
     | 
| 
      
 117 
     | 
    
         
            +
                    @apis_from_routes
         
     | 
| 
      
 118 
     | 
    
         
            +
                  end
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
                end
         
     | 
| 
      
 121 
     | 
    
         
            +
              end
         
     | 
| 
      
 122 
     | 
    
         
            +
            end
         
     | 
| 
      
 123 
     | 
    
         
            +
             
     | 
| 
      
 124 
     | 
    
         
            +
            if ENV["RESTAPI_RECORD"]
         
     | 
| 
      
 125 
     | 
    
         
            +
              Restapi.configuration.force_dsl = true
         
     | 
| 
      
 126 
     | 
    
         
            +
              at_exit do
         
     | 
| 
      
 127 
     | 
    
         
            +
                record_params, record_examples = false, false
         
     | 
| 
      
 128 
     | 
    
         
            +
                case ENV["RESTAPI_RECORD"]
         
     | 
| 
      
 129 
     | 
    
         
            +
                when "params"   then record_params = true
         
     | 
| 
      
 130 
     | 
    
         
            +
                when "examples" then record_examples = true
         
     | 
| 
      
 131 
     | 
    
         
            +
                when "all"      then record_params = true, record_examples = true
         
     | 
| 
      
 132 
     | 
    
         
            +
                end
         
     | 
| 
      
 133 
     | 
    
         
            +
             
     | 
| 
      
 134 
     | 
    
         
            +
                if record_examples
         
     | 
| 
      
 135 
     | 
    
         
            +
                  puts "Writing examples to a file"
         
     | 
| 
      
 136 
     | 
    
         
            +
                  Restapi::Extractor.write_examples
         
     | 
| 
      
 137 
     | 
    
         
            +
                end
         
     | 
| 
      
 138 
     | 
    
         
            +
                if record_params
         
     | 
| 
      
 139 
     | 
    
         
            +
                  puts "Updating auto-generated documentation"
         
     | 
| 
      
 140 
     | 
    
         
            +
                  Restapi::Extractor.write_docs
         
     | 
| 
      
 141 
     | 
    
         
            +
                end
         
     | 
| 
      
 142 
     | 
    
         
            +
              end
         
     | 
| 
      
 143 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,113 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Restapi
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Extractor
         
     | 
| 
      
 3 
     | 
    
         
            +
                class Collector
         
     | 
| 
      
 4 
     | 
    
         
            +
                  attr_reader :descriptions, :records
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                  def initialize
         
     | 
| 
      
 7 
     | 
    
         
            +
                    @api_controllers_paths = Restapi.api_controllers_paths
         
     | 
| 
      
 8 
     | 
    
         
            +
                    @ignored = Restapi.configuration.ignored_by_recorder
         
     | 
| 
      
 9 
     | 
    
         
            +
                    @descriptions = Hash.new do |h, k|
         
     | 
| 
      
 10 
     | 
    
         
            +
                      h[k] = {:params => {}, :errors => Set.new}
         
     | 
| 
      
 11 
     | 
    
         
            +
                    end
         
     | 
| 
      
 12 
     | 
    
         
            +
                    @records = Hash.new { |h,k| h[k] = [] }
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  def controller_full_path(controller)
         
     | 
| 
      
 16 
     | 
    
         
            +
                    File.join(Rails.root, "app", "controllers", "#{controller.controller_path}_controller.rb")
         
     | 
| 
      
 17 
     | 
    
         
            +
                  end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  def ignore_call?(record)
         
     | 
| 
      
 20 
     | 
    
         
            +
                    return true unless record[:controller]
         
     | 
| 
      
 21 
     | 
    
         
            +
                    return true if @ignored.include?(record[:controller].name)
         
     | 
| 
      
 22 
     | 
    
         
            +
                    return true if @ignored.include?("#{record[:controller].name}##{record[:action]}")
         
     | 
| 
      
 23 
     | 
    
         
            +
                    return true unless @api_controllers_paths.include?(controller_full_path(record[:controller]))
         
     | 
| 
      
 24 
     | 
    
         
            +
                  end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                  def handle_record(record)
         
     | 
| 
      
 27 
     | 
    
         
            +
                    add_to_records(record)
         
     | 
| 
      
 28 
     | 
    
         
            +
                    if ignore_call?(record)
         
     | 
| 
      
 29 
     | 
    
         
            +
                      Extractor.logger.info("REST_API: skipping #{record_to_s(record)}")
         
     | 
| 
      
 30 
     | 
    
         
            +
                    else
         
     | 
| 
      
 31 
     | 
    
         
            +
                      refine_description(record)
         
     | 
| 
      
 32 
     | 
    
         
            +
                    end
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                  def add_to_records(record)
         
     | 
| 
      
 36 
     | 
    
         
            +
                    key = "#{record[:controller].controller_name}##{record[:action]}"
         
     | 
| 
      
 37 
     | 
    
         
            +
                    @records[key] << record
         
     | 
| 
      
 38 
     | 
    
         
            +
                  end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                  def refine_description(record)
         
     | 
| 
      
 41 
     | 
    
         
            +
                    description = @descriptions["#{record[:controller].name}##{record[:action]}"]
         
     | 
| 
      
 42 
     | 
    
         
            +
                    description[:controller] ||= record[:controller]
         
     | 
| 
      
 43 
     | 
    
         
            +
                    description[:action] ||= record[:action]
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                    refine_errors_description(description, record)
         
     | 
| 
      
 46 
     | 
    
         
            +
                    refine_params_description(description[:params], record[:params])
         
     | 
| 
      
 47 
     | 
    
         
            +
                  end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                  def refine_errors_description(description, record)
         
     | 
| 
      
 50 
     | 
    
         
            +
                    if record[:code].to_i >= 300 && !description[:errors].any? { |e| e[:code].to_i == record[:code].to_i }
         
     | 
| 
      
 51 
     | 
    
         
            +
                      description[:errors] << {:code => record[:code]}
         
     | 
| 
      
 52 
     | 
    
         
            +
                    end
         
     | 
| 
      
 53 
     | 
    
         
            +
                  end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                  def refine_params_description(params_desc, recorded_params)
         
     | 
| 
      
 56 
     | 
    
         
            +
                    recorded_params.each do |key, value|
         
     | 
| 
      
 57 
     | 
    
         
            +
                      params_desc[key] ||= {}
         
     | 
| 
      
 58 
     | 
    
         
            +
                      param_desc = params_desc[key]
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                      if value.nil?
         
     | 
| 
      
 61 
     | 
    
         
            +
                        param_desc[:allow_nil] = true
         
     | 
| 
      
 62 
     | 
    
         
            +
                      else
         
     | 
| 
      
 63 
     | 
    
         
            +
                        # we specify what type it might be. At the end the first type
         
     | 
| 
      
 64 
     | 
    
         
            +
                        # that left is taken as the more general one
         
     | 
| 
      
 65 
     | 
    
         
            +
                        unless param_desc[:type]
         
     | 
| 
      
 66 
     | 
    
         
            +
                          param_desc[:type] = [:bool, :number]
         
     | 
| 
      
 67 
     | 
    
         
            +
                          param_desc[:type] << Hash if value.is_a? Hash
         
     | 
| 
      
 68 
     | 
    
         
            +
                          param_desc[:type] << :undef
         
     | 
| 
      
 69 
     | 
    
         
            +
                        end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                        if param_desc[:type].first == :bool && (! [true, false].include?(value))
         
     | 
| 
      
 72 
     | 
    
         
            +
                          param_desc[:type].shift
         
     | 
| 
      
 73 
     | 
    
         
            +
                        end
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
                        if param_desc[:type].first == :number && (key.to_s !~ /id$/ || !Restapi::Validator::NumberValidator.validate(value))
         
     | 
| 
      
 76 
     | 
    
         
            +
                          param_desc[:type].shift
         
     | 
| 
      
 77 
     | 
    
         
            +
                        end
         
     | 
| 
      
 78 
     | 
    
         
            +
                      end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                      if value.is_a? Hash
         
     | 
| 
      
 81 
     | 
    
         
            +
                        param_desc[:nested] ||= {}
         
     | 
| 
      
 82 
     | 
    
         
            +
                        refine_params_description(param_desc[:nested], value)
         
     | 
| 
      
 83 
     | 
    
         
            +
                      end
         
     | 
| 
      
 84 
     | 
    
         
            +
                    end
         
     | 
| 
      
 85 
     | 
    
         
            +
                  end
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
                  def finalize_descriptions
         
     | 
| 
      
 88 
     | 
    
         
            +
                    @descriptions.each do |method, desc|
         
     | 
| 
      
 89 
     | 
    
         
            +
                      add_routes_info(desc)
         
     | 
| 
      
 90 
     | 
    
         
            +
                    end
         
     | 
| 
      
 91 
     | 
    
         
            +
                    return @descriptions
         
     | 
| 
      
 92 
     | 
    
         
            +
                  end
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
                  def add_routes_info(desc)
         
     | 
| 
      
 95 
     | 
    
         
            +
                    api_prefix = Restapi.configuration.api_base_url.sub(/\/$/,"")
         
     | 
| 
      
 96 
     | 
    
         
            +
                    desc[:api] = Restapi::Extractor.apis_from_routes[[desc[:controller].name, desc[:action]]]
         
     | 
| 
      
 97 
     | 
    
         
            +
                    if desc[:api]
         
     | 
| 
      
 98 
     | 
    
         
            +
                      desc[:params].each do |name, param|
         
     | 
| 
      
 99 
     | 
    
         
            +
                        if desc[:api].all? { |a| a[:path].include?(":#{name}") }
         
     | 
| 
      
 100 
     | 
    
         
            +
                          param[:required] = true
         
     | 
| 
      
 101 
     | 
    
         
            +
                        end
         
     | 
| 
      
 102 
     | 
    
         
            +
                      end
         
     | 
| 
      
 103 
     | 
    
         
            +
                    end
         
     | 
| 
      
 104 
     | 
    
         
            +
                  end
         
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
      
 106 
     | 
    
         
            +
                  def record_to_s(record)
         
     | 
| 
      
 107 
     | 
    
         
            +
                    "#{record[:controller]}##{record[:action]}"
         
     | 
| 
      
 108 
     | 
    
         
            +
                  end
         
     | 
| 
      
 109 
     | 
    
         
            +
             
     | 
| 
      
 110 
     | 
    
         
            +
                end
         
     | 
| 
      
 111 
     | 
    
         
            +
              end
         
     | 
| 
      
 112 
     | 
    
         
            +
            end
         
     | 
| 
      
 113 
     | 
    
         
            +
             
     | 
| 
         @@ -0,0 +1,122 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Restapi
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Extractor
         
     | 
| 
      
 3 
     | 
    
         
            +
                class Recorder
         
     | 
| 
      
 4 
     | 
    
         
            +
                  def initialize
         
     | 
| 
      
 5 
     | 
    
         
            +
                    @ignored_params = [:controller, :action]
         
     | 
| 
      
 6 
     | 
    
         
            +
                  end
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  def analyse_env(env)
         
     | 
| 
      
 9 
     | 
    
         
            +
                    @verb = env["REQUEST_METHOD"].to_sym
         
     | 
| 
      
 10 
     | 
    
         
            +
                    @path = env["PATH_INFO"].sub(/^\/*/,"/")
         
     | 
| 
      
 11 
     | 
    
         
            +
                    @query = env["QUERY_STRING"] unless env["QUERY_STRING"].blank?
         
     | 
| 
      
 12 
     | 
    
         
            +
                    @params = Rack::Utils.parse_nested_query(@query)
         
     | 
| 
      
 13 
     | 
    
         
            +
                    @params.merge!(env["action_dispatch.request.request_parameters"] || {})
         
     | 
| 
      
 14 
     | 
    
         
            +
                    if data = parse_data(env["rack.input"].read)
         
     | 
| 
      
 15 
     | 
    
         
            +
                      @request_data = data
         
     | 
| 
      
 16 
     | 
    
         
            +
                    end
         
     | 
| 
      
 17 
     | 
    
         
            +
                  end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  def analyse_controller(controller)
         
     | 
| 
      
 20 
     | 
    
         
            +
                    @controller = controller.class
         
     | 
| 
      
 21 
     | 
    
         
            +
                    @action = controller.params[:action]
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                  def analyse_response(response)
         
     | 
| 
      
 25 
     | 
    
         
            +
                    if response.last.respond_to?(:body) && data = parse_data(response.last.body)
         
     | 
| 
      
 26 
     | 
    
         
            +
                      @response_data = data
         
     | 
| 
      
 27 
     | 
    
         
            +
                    end
         
     | 
| 
      
 28 
     | 
    
         
            +
                    @code = response.first
         
     | 
| 
      
 29 
     | 
    
         
            +
                  end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                  def analyze_functional_test(test_context)
         
     | 
| 
      
 32 
     | 
    
         
            +
                    request, response = test_context.request, test_context.response
         
     | 
| 
      
 33 
     | 
    
         
            +
                    @verb = request.request_method.to_sym
         
     | 
| 
      
 34 
     | 
    
         
            +
                    @path = request.path
         
     | 
| 
      
 35 
     | 
    
         
            +
                    @params = request.request_parameters
         
     | 
| 
      
 36 
     | 
    
         
            +
                    if [:POST, :PUT].include?(@verb)
         
     | 
| 
      
 37 
     | 
    
         
            +
                      @request_data = @params
         
     | 
| 
      
 38 
     | 
    
         
            +
                    else
         
     | 
| 
      
 39 
     | 
    
         
            +
                      @query = request.query_string
         
     | 
| 
      
 40 
     | 
    
         
            +
                    end
         
     | 
| 
      
 41 
     | 
    
         
            +
                    @response_data = parse_data(response.body)
         
     | 
| 
      
 42 
     | 
    
         
            +
                    @code = response.code
         
     | 
| 
      
 43 
     | 
    
         
            +
                  end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                  def parse_data(data)
         
     | 
| 
      
 46 
     | 
    
         
            +
                    return nil if data.to_s =~ /^\s*$/
         
     | 
| 
      
 47 
     | 
    
         
            +
                    JSON.parse(data)
         
     | 
| 
      
 48 
     | 
    
         
            +
                  rescue Exception => e
         
     | 
| 
      
 49 
     | 
    
         
            +
                    data
         
     | 
| 
      
 50 
     | 
    
         
            +
                  end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                  def reformat_data(data)
         
     | 
| 
      
 53 
     | 
    
         
            +
                    parsed = parse_data(data)
         
     | 
| 
      
 54 
     | 
    
         
            +
                    case parsed
         
     | 
| 
      
 55 
     | 
    
         
            +
                    when nil
         
     | 
| 
      
 56 
     | 
    
         
            +
                      nil
         
     | 
| 
      
 57 
     | 
    
         
            +
                    when String
         
     | 
| 
      
 58 
     | 
    
         
            +
                      parsed
         
     | 
| 
      
 59 
     | 
    
         
            +
                    else
         
     | 
| 
      
 60 
     | 
    
         
            +
                      JSON.pretty_generate().gsub(/: \[\s*\]/,": []").gsub(/\{\s*\}/,"{}")
         
     | 
| 
      
 61 
     | 
    
         
            +
                    end
         
     | 
| 
      
 62 
     | 
    
         
            +
                  end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                  def record
         
     | 
| 
      
 65 
     | 
    
         
            +
                    if @controller
         
     | 
| 
      
 66 
     | 
    
         
            +
                      {:controller => @controller,
         
     | 
| 
      
 67 
     | 
    
         
            +
                       :action => @action,
         
     | 
| 
      
 68 
     | 
    
         
            +
                       :verb => @verb,
         
     | 
| 
      
 69 
     | 
    
         
            +
                       :path => @path,
         
     | 
| 
      
 70 
     | 
    
         
            +
                       :params => @params,
         
     | 
| 
      
 71 
     | 
    
         
            +
                       :query => @query,
         
     | 
| 
      
 72 
     | 
    
         
            +
                       :request_data => @request_data,
         
     | 
| 
      
 73 
     | 
    
         
            +
                       :response_data => @response_data,
         
     | 
| 
      
 74 
     | 
    
         
            +
                       :code => @code}
         
     | 
| 
      
 75 
     | 
    
         
            +
                    else
         
     | 
| 
      
 76 
     | 
    
         
            +
                      nil
         
     | 
| 
      
 77 
     | 
    
         
            +
                    end
         
     | 
| 
      
 78 
     | 
    
         
            +
                  end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                  protected
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                  def api_description
         
     | 
| 
      
 83 
     | 
    
         
            +
                  end
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                  class Middleware
         
     | 
| 
      
 86 
     | 
    
         
            +
                    def initialize(app)
         
     | 
| 
      
 87 
     | 
    
         
            +
                      @app = app
         
     | 
| 
      
 88 
     | 
    
         
            +
                    end
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
                    def call(env)
         
     | 
| 
      
 91 
     | 
    
         
            +
                      analyze(env) do
         
     | 
| 
      
 92 
     | 
    
         
            +
                        @app.call(env)
         
     | 
| 
      
 93 
     | 
    
         
            +
                      end
         
     | 
| 
      
 94 
     | 
    
         
            +
                    end
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                    def analyze(env, &block)
         
     | 
| 
      
 97 
     | 
    
         
            +
                      Restapi::Extractor.call_recorder.analyse_env(env)
         
     | 
| 
      
 98 
     | 
    
         
            +
                      response = block.call
         
     | 
| 
      
 99 
     | 
    
         
            +
                      Restapi::Extractor.call_recorder.analyse_response(response)
         
     | 
| 
      
 100 
     | 
    
         
            +
                      response
         
     | 
| 
      
 101 
     | 
    
         
            +
                    ensure
         
     | 
| 
      
 102 
     | 
    
         
            +
                      Restapi::Extractor.call_finished
         
     | 
| 
      
 103 
     | 
    
         
            +
                    end
         
     | 
| 
      
 104 
     | 
    
         
            +
                  end
         
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
      
 106 
     | 
    
         
            +
                  module FunctionalTestRecording
         
     | 
| 
      
 107 
     | 
    
         
            +
                    def self.included(base)
         
     | 
| 
      
 108 
     | 
    
         
            +
                      base.alias_method_chain :process, :api_recording          
         
     | 
| 
      
 109 
     | 
    
         
            +
                    end
         
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
      
 111 
     | 
    
         
            +
                    def process_with_api_recording(*args) # action, parameters = nil, session = nil, flash = nil, http_method = 'GET')
         
     | 
| 
      
 112 
     | 
    
         
            +
                      process_without_api_recording(*args)
         
     | 
| 
      
 113 
     | 
    
         
            +
                      Restapi::Extractor.call_recorder.analyze_functional_test(self)
         
     | 
| 
      
 114 
     | 
    
         
            +
                    ensure
         
     | 
| 
      
 115 
     | 
    
         
            +
                      Restapi::Extractor.call_finished
         
     | 
| 
      
 116 
     | 
    
         
            +
                    end
         
     | 
| 
      
 117 
     | 
    
         
            +
                  end
         
     | 
| 
      
 118 
     | 
    
         
            +
             
     | 
| 
      
 119 
     | 
    
         
            +
                end
         
     | 
| 
      
 120 
     | 
    
         
            +
             
     | 
| 
      
 121 
     | 
    
         
            +
              end
         
     | 
| 
      
 122 
     | 
    
         
            +
            end
         
     |