jmoses_apipie-rails 0.0.23

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. data/.gitignore +11 -0
  2. data/.rspec +2 -0
  3. data/.travis.yml +4 -0
  4. data/APACHE-LICENSE-2.0 +202 -0
  5. data/CHANGELOG +55 -0
  6. data/Gemfile +3 -0
  7. data/MIT-LICENSE +20 -0
  8. data/NOTICE +4 -0
  9. data/README.rst +938 -0
  10. data/Rakefile +13 -0
  11. data/apipie-rails.gemspec +26 -0
  12. data/app/controllers/apipie/apipies_controller.rb +105 -0
  13. data/app/public/apipie/javascripts/apipie.js +6 -0
  14. data/app/public/apipie/javascripts/bundled/bootstrap-collapse.js +138 -0
  15. data/app/public/apipie/javascripts/bundled/bootstrap.js +1726 -0
  16. data/app/public/apipie/javascripts/bundled/jquery-1.7.2.js +9404 -0
  17. data/app/public/apipie/javascripts/bundled/prettify.js +28 -0
  18. data/app/public/apipie/stylesheets/application.css +20 -0
  19. data/app/public/apipie/stylesheets/bundled/bootstrap-responsive.min.css +12 -0
  20. data/app/public/apipie/stylesheets/bundled/bootstrap.min.css +689 -0
  21. data/app/public/apipie/stylesheets/bundled/prettify.css +30 -0
  22. data/app/views/apipie/apipies/_disqus.html.erb +11 -0
  23. data/app/views/apipie/apipies/_params.html.erb +29 -0
  24. data/app/views/apipie/apipies/_params_plain.html.erb +16 -0
  25. data/app/views/apipie/apipies/apipie_404.html.erb +12 -0
  26. data/app/views/apipie/apipies/getting_started.html.erb +4 -0
  27. data/app/views/apipie/apipies/index.html.erb +43 -0
  28. data/app/views/apipie/apipies/method.html.erb +71 -0
  29. data/app/views/apipie/apipies/plain.html.erb +70 -0
  30. data/app/views/apipie/apipies/resource.html.erb +98 -0
  31. data/app/views/apipie/apipies/static.html.erb +101 -0
  32. data/app/views/layouts/apipie/apipie.html.erb +26 -0
  33. data/lib/apipie-rails.rb +15 -0
  34. data/lib/apipie/apipie_module.rb +62 -0
  35. data/lib/apipie/application.rb +334 -0
  36. data/lib/apipie/client/generator.rb +135 -0
  37. data/lib/apipie/configuration.rb +128 -0
  38. data/lib/apipie/dsl_definition.rb +379 -0
  39. data/lib/apipie/error_description.rb +25 -0
  40. data/lib/apipie/errors.rb +38 -0
  41. data/lib/apipie/extractor.rb +151 -0
  42. data/lib/apipie/extractor/collector.rb +113 -0
  43. data/lib/apipie/extractor/recorder.rb +124 -0
  44. data/lib/apipie/extractor/writer.rb +367 -0
  45. data/lib/apipie/helpers.rb +52 -0
  46. data/lib/apipie/markup.rb +48 -0
  47. data/lib/apipie/method_description.rb +191 -0
  48. data/lib/apipie/param_description.rb +204 -0
  49. data/lib/apipie/railtie.rb +9 -0
  50. data/lib/apipie/resource_description.rb +102 -0
  51. data/lib/apipie/routing.rb +15 -0
  52. data/lib/apipie/see_description.rb +39 -0
  53. data/lib/apipie/static_dispatcher.rb +59 -0
  54. data/lib/apipie/validator.rb +310 -0
  55. data/lib/apipie/version.rb +3 -0
  56. data/lib/generators/apipie/install/README +6 -0
  57. data/lib/generators/apipie/install/install_generator.rb +25 -0
  58. data/lib/generators/apipie/install/templates/initializer.rb.erb +7 -0
  59. data/lib/tasks/apipie.rake +166 -0
  60. data/rel-eng/packages/.readme +3 -0
  61. data/rel-eng/packages/rubygem-apipie-rails +1 -0
  62. data/rel-eng/tito.props +5 -0
  63. data/spec/controllers/api/v1/architectures_controller_spec.rb +30 -0
  64. data/spec/controllers/api/v2/architectures_controller_spec.rb +12 -0
  65. data/spec/controllers/api/v2/nested/resources_controller_spec.rb +11 -0
  66. data/spec/controllers/apipies_controller_spec.rb +141 -0
  67. data/spec/controllers/concerns_controller_spec.rb +42 -0
  68. data/spec/controllers/users_controller_spec.rb +473 -0
  69. data/spec/dummy/Rakefile +7 -0
  70. data/spec/dummy/app/controllers/api/base_controller.rb +4 -0
  71. data/spec/dummy/app/controllers/api/v1/architectures_controller.rb +42 -0
  72. data/spec/dummy/app/controllers/api/v1/base_controller.rb +11 -0
  73. data/spec/dummy/app/controllers/api/v2/architectures_controller.rb +30 -0
  74. data/spec/dummy/app/controllers/api/v2/base_controller.rb +11 -0
  75. data/spec/dummy/app/controllers/api/v2/nested/architectures_controller.rb +30 -0
  76. data/spec/dummy/app/controllers/api/v2/nested/resources_controller.rb +33 -0
  77. data/spec/dummy/app/controllers/application_controller.rb +6 -0
  78. data/spec/dummy/app/controllers/concerns/sample_controller.rb +39 -0
  79. data/spec/dummy/app/controllers/concerns_controller.rb +8 -0
  80. data/spec/dummy/app/controllers/twitter_example_controller.rb +302 -0
  81. data/spec/dummy/app/controllers/users_controller.rb +251 -0
  82. data/spec/dummy/app/views/layouts/application.html.erb +21 -0
  83. data/spec/dummy/config.ru +4 -0
  84. data/spec/dummy/config/application.rb +45 -0
  85. data/spec/dummy/config/boot.rb +10 -0
  86. data/spec/dummy/config/database.yml +21 -0
  87. data/spec/dummy/config/environment.rb +8 -0
  88. data/spec/dummy/config/environments/development.rb +25 -0
  89. data/spec/dummy/config/environments/production.rb +49 -0
  90. data/spec/dummy/config/environments/test.rb +35 -0
  91. data/spec/dummy/config/initializers/apipie.rb +102 -0
  92. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  93. data/spec/dummy/config/initializers/inflections.rb +10 -0
  94. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  95. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  96. data/spec/dummy/config/initializers/session_store.rb +8 -0
  97. data/spec/dummy/config/locales/en.yml +5 -0
  98. data/spec/dummy/config/routes.rb +22 -0
  99. data/spec/dummy/db/.gitkeep +0 -0
  100. data/spec/dummy/doc/apipie_examples.yml +28 -0
  101. data/spec/dummy/public/404.html +26 -0
  102. data/spec/dummy/public/422.html +26 -0
  103. data/spec/dummy/public/500.html +26 -0
  104. data/spec/dummy/public/favicon.ico +0 -0
  105. data/spec/dummy/public/javascripts/application.js +2 -0
  106. data/spec/dummy/public/javascripts/controls.js +965 -0
  107. data/spec/dummy/public/javascripts/dragdrop.js +974 -0
  108. data/spec/dummy/public/javascripts/effects.js +1123 -0
  109. data/spec/dummy/public/javascripts/prototype.js +6001 -0
  110. data/spec/dummy/public/javascripts/rails.js +202 -0
  111. data/spec/dummy/public/stylesheets/.gitkeep +0 -0
  112. data/spec/dummy/script/rails +6 -0
  113. data/spec/lib/application_spec.rb +38 -0
  114. data/spec/lib/method_description_spec.rb +30 -0
  115. data/spec/lib/param_description_spec.rb +174 -0
  116. data/spec/lib/param_group_spec.rb +45 -0
  117. data/spec/lib/resource_description_spec.rb +30 -0
  118. data/spec/lib/validator_spec.rb +46 -0
  119. data/spec/spec_helper.rb +32 -0
  120. metadata +337 -0
@@ -0,0 +1,25 @@
1
+ module Apipie
2
+
3
+ class ErrorDescription
4
+
5
+ attr_reader :code, :description
6
+
7
+ def initialize(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 "ApipieError: Bad use of error method."
14
+ end
15
+ @code = args[:code] || args['code']
16
+ @description = args[:desc] || args[:description] || args['desc'] || args['description']
17
+ end
18
+
19
+ def to_json
20
+ {:code => code, :description => description}
21
+ end
22
+
23
+ end
24
+
25
+ end
@@ -0,0 +1,38 @@
1
+ module Apipie
2
+
3
+ class Error < StandardError
4
+ end
5
+
6
+ class ParamError < Error
7
+ end
8
+
9
+ # abstract
10
+ class DefinedParamError < ParamError
11
+ attr_accessor :param
12
+
13
+ def initialize(param)
14
+ @param = param
15
+ end
16
+ end
17
+
18
+ class ParamMissing < DefinedParamError
19
+ def to_s
20
+ "Missing parameter #{@param}"
21
+ end
22
+ end
23
+
24
+ class ParamInvalid < DefinedParamError
25
+ attr_accessor :value, :error
26
+
27
+ def initialize(param, value, error)
28
+ super param
29
+ @value = value
30
+ @error = error
31
+ end
32
+
33
+ def to_s
34
+ "Invalid parameter '#{@param}' value #{@value.inspect}: #{@error}"
35
+ end
36
+ end
37
+
38
+ end
@@ -0,0 +1,151 @@
1
+ require 'singleton'
2
+ require 'fileutils'
3
+ require 'set'
4
+ require 'yaml'
5
+ require 'apipie/extractor/recorder'
6
+ require 'apipie/extractor/writer'
7
+ require 'apipie/extractor/collector'
8
+
9
+ class Apipie::Railtie
10
+ if ENV["APIPIE_RECORD"]
11
+ initializer 'apipie.extractor' do |app|
12
+ ActiveSupport.on_load :action_controller do
13
+ before_filter do |controller|
14
+ Apipie::Extractor.call_recorder.analyse_controller(controller)
15
+ end
16
+ end
17
+ app.middleware.use ::Apipie::Extractor::Recorder::Middleware
18
+ ActionController::TestCase::Behavior.instance_eval do
19
+ include Apipie::Extractor::Recorder::FunctionalTestRecording
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ module Apipie
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[:apipie_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[:apipie_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
+ # TODO: this is a loooooooooong method :)
57
+ def apis_from_routes
58
+ return @apis_from_routes if @apis_from_routes
59
+
60
+ api_prefix = Apipie.api_base_url.sub(/\/$/,"")
61
+ all_routes = Rails.application.routes.routes.map do |r|
62
+ {
63
+ :verb => case r.verb
64
+ when Regexp then r.verb.source[/\w+/]
65
+ else r.verb.to_s
66
+ end,
67
+ :path => case
68
+ when r.path.respond_to?(:spec) then r.path.spec.to_s
69
+ else r.path.to_s
70
+ end,
71
+ :controller => r.requirements[:controller],
72
+ :action => r.requirements[:action]
73
+ }
74
+
75
+
76
+ end
77
+ api_routes = all_routes.find_all do |r|
78
+ r[:path].starts_with?(Apipie.api_base_url)
79
+ end
80
+
81
+ @apis_from_routes = Hash.new { |h, k| h[k] = [] }
82
+
83
+ api_routes.each do |route|
84
+ controller_path, action = route[:controller], route[:action]
85
+ next unless controller_path && action
86
+
87
+ controller = "#{controller_path}_controller".camelize
88
+
89
+ path = if /^#{Regexp.escape(api_prefix)}(.*)$/ =~ route[:path]
90
+ $1.sub!(/\(\.:format\)$/,"")
91
+ else
92
+ nil
93
+ end
94
+
95
+ if route[:verb].present?
96
+ @apis_from_routes[[controller, action]] << {:method => route[:verb], :path => path}
97
+ end
98
+ end
99
+ @apis_from_routes
100
+
101
+ resource_descriptions = Apipie.resource_descriptions.values.map(&:values).flatten
102
+ method_descriptions = resource_descriptions.map(&:method_descriptions).flatten
103
+ apis_from_docs = method_descriptions.reduce({}) do |h, desc|
104
+ apis = desc.method_apis_to_json.map do |api|
105
+ { :method => api[:http_method],
106
+ :path => api[:api_url],
107
+ :desc => api[:short_description] }
108
+ end
109
+ h.update(desc.id => apis)
110
+ end
111
+
112
+ @apis_from_routes.each do |(controller, action), new_apis|
113
+ method_key = "#{Apipie.get_resource_name(controller.constantize)}##{action}"
114
+ old_apis = apis_from_docs[method_key] || []
115
+ new_apis.each do |new_api|
116
+ new_api[:path].sub!(/\(\.:format\)$/,"")
117
+ old_api = old_apis.find do |api|
118
+ api[:path] == "#{api_prefix}#{new_api[:path]}"
119
+ end
120
+ if old_api
121
+ new_api[:desc] = old_api[:desc]
122
+ end
123
+ end
124
+ end
125
+ @apis_from_routes
126
+ end
127
+
128
+ end
129
+ end
130
+ end
131
+
132
+ if ENV["APIPIE_RECORD"]
133
+ Apipie.configuration.force_dsl = true
134
+ at_exit do
135
+ record_params, record_examples = false, false
136
+ case ENV["APIPIE_RECORD"]
137
+ when "params" then record_params = true
138
+ when "examples" then record_examples = true
139
+ when "all" then record_params = true, record_examples = true
140
+ end
141
+
142
+ if record_examples
143
+ puts "Writing examples to a file"
144
+ Apipie::Extractor.write_examples
145
+ end
146
+ if record_params
147
+ puts "Updating auto-generated documentation"
148
+ Apipie::Extractor.write_docs
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,113 @@
1
+ module Apipie
2
+ module Extractor
3
+ class Collector
4
+ attr_reader :descriptions, :records
5
+
6
+ def initialize
7
+ @api_controllers_paths = Apipie.api_controllers_paths
8
+ @ignored = Apipie.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?("#{Apipie.get_resource_name(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 = "#{Apipie.get_resource_name(record[:controller])}##{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$/ || !Apipie::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 = Apipie.api_base_url.sub(/\/$/,"")
96
+ desc[:api] = Apipie::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,124 @@
1
+ module Apipie
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
+ env["rack.input"].rewind
17
+ end
18
+ end
19
+
20
+ def analyse_controller(controller)
21
+ @controller = controller.class
22
+ @action = controller.params[:action]
23
+ end
24
+
25
+ def analyse_response(response)
26
+ if response.last.respond_to?(:body) && data = parse_data(response.last.body)
27
+ @response_data = data
28
+ end
29
+ @code = response.first
30
+ end
31
+
32
+ def analyze_functional_test(test_context)
33
+ request, response = test_context.request, test_context.response
34
+ @verb = request.request_method.to_sym
35
+ @path = request.path
36
+ @params = request.request_parameters
37
+ if [:POST, :PUT].include?(@verb)
38
+ @request_data = @params
39
+ else
40
+ @query = request.query_string
41
+ end
42
+ @response_data = parse_data(response.body)
43
+ @code = response.code
44
+ end
45
+
46
+ def parse_data(data)
47
+ return nil if data.to_s =~ /^\s*$/
48
+ JSON.parse(data)
49
+ rescue Exception => e
50
+ data
51
+ end
52
+
53
+ def reformat_data(data)
54
+ parsed = parse_data(data)
55
+ case parsed
56
+ when nil
57
+ nil
58
+ when String
59
+ parsed
60
+ else
61
+ JSON.pretty_generate().gsub(/: \[\s*\]/,": []").gsub(/\{\s*\}/,"{}")
62
+ end
63
+ end
64
+
65
+ def record
66
+ if @controller
67
+ {:controller => @controller,
68
+ :action => @action,
69
+ :verb => @verb,
70
+ :path => @path,
71
+ :params => @params,
72
+ :query => @query,
73
+ :request_data => @request_data,
74
+ :response_data => @response_data,
75
+ :code => @code}
76
+ else
77
+ nil
78
+ end
79
+ end
80
+
81
+ protected
82
+
83
+ def api_description
84
+ end
85
+
86
+ class Middleware
87
+ def initialize(app)
88
+ @app = app
89
+ end
90
+
91
+ def call(env)
92
+ analyze(env) do
93
+ @app.call(env)
94
+ end
95
+ end
96
+
97
+ def analyze(env, &block)
98
+ Apipie::Extractor.call_recorder.analyse_env(env)
99
+ response = block.call
100
+ Apipie::Extractor.call_recorder.analyse_response(response)
101
+ response
102
+ ensure
103
+ Apipie::Extractor.call_finished
104
+ end
105
+ end
106
+
107
+ module FunctionalTestRecording
108
+ def self.included(base)
109
+ base.alias_method_chain :process, :api_recording
110
+ end
111
+
112
+ def process_with_api_recording(*args) # action, parameters = nil, session = nil, flash = nil, http_method = 'GET')
113
+ ret = process_without_api_recording(*args)
114
+ Apipie::Extractor.call_recorder.analyze_functional_test(self)
115
+ ret
116
+ ensure
117
+ Apipie::Extractor.call_finished
118
+ end
119
+ end
120
+
121
+ end
122
+
123
+ end
124
+ end