rspec-api-documentation 1.1.1.alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/lib/rspec_api_documentation.rb +63 -0
  3. data/lib/rspec_api_documentation/api_documentation.rb +38 -0
  4. data/lib/rspec_api_documentation/api_formatter.rb +45 -0
  5. data/lib/rspec_api_documentation/client_base.rb +82 -0
  6. data/lib/rspec_api_documentation/configuration.rb +84 -0
  7. data/lib/rspec_api_documentation/curl.rb +61 -0
  8. data/lib/rspec_api_documentation/dsl.rb +17 -0
  9. data/lib/rspec_api_documentation/dsl/callback.rb +25 -0
  10. data/lib/rspec_api_documentation/dsl/endpoint.rb +134 -0
  11. data/lib/rspec_api_documentation/dsl/resource.rb +83 -0
  12. data/lib/rspec_api_documentation/example.rb +49 -0
  13. data/lib/rspec_api_documentation/headers.rb +32 -0
  14. data/lib/rspec_api_documentation/index.rb +7 -0
  15. data/lib/rspec_api_documentation/oauth2_mac_client.rb +70 -0
  16. data/lib/rspec_api_documentation/rack_test_client.rb +58 -0
  17. data/lib/rspec_api_documentation/railtie.rb +7 -0
  18. data/lib/rspec_api_documentation/test_server.rb +31 -0
  19. data/lib/rspec_api_documentation/views/html_example.rb +16 -0
  20. data/lib/rspec_api_documentation/views/html_index.rb +14 -0
  21. data/lib/rspec_api_documentation/views/markup_example.rb +58 -0
  22. data/lib/rspec_api_documentation/views/markup_index.rb +21 -0
  23. data/lib/rspec_api_documentation/views/textile_example.rb +16 -0
  24. data/lib/rspec_api_documentation/views/textile_index.rb +14 -0
  25. data/lib/rspec_api_documentation/writers/combined_json_writer.rb +20 -0
  26. data/lib/rspec_api_documentation/writers/combined_text_writer.rb +106 -0
  27. data/lib/rspec_api_documentation/writers/formatter.rb +11 -0
  28. data/lib/rspec_api_documentation/writers/general_markup_writer.rb +42 -0
  29. data/lib/rspec_api_documentation/writers/html_writer.rb +21 -0
  30. data/lib/rspec_api_documentation/writers/index_writer.rb +16 -0
  31. data/lib/rspec_api_documentation/writers/json_iodocs_writer.rb +111 -0
  32. data/lib/rspec_api_documentation/writers/json_writer.rb +111 -0
  33. data/lib/rspec_api_documentation/writers/textile_writer.rb +21 -0
  34. data/lib/tasks/docs.rake +7 -0
  35. data/templates/rspec_api_documentation/html_example.mustache +106 -0
  36. data/templates/rspec_api_documentation/html_index.mustache +34 -0
  37. data/templates/rspec_api_documentation/textile_example.mustache +68 -0
  38. data/templates/rspec_api_documentation/textile_index.mustache +10 -0
  39. metadata +267 -0
@@ -0,0 +1,83 @@
1
+ module RspecApiDocumentation::DSL
2
+ module Resource
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def self.define_action(method)
7
+ define_method method do |*args, &block|
8
+ options = if args.last.is_a?(Hash) then args.pop else {} end
9
+ options[:method] = method
10
+ options[:route] = args.first
11
+ options[:api_doc_dsl] = :endpoint
12
+ args.push(options)
13
+ args[0] = "#{method.to_s.upcase} #{args[0]}"
14
+ context(*args, &block)
15
+ end
16
+ end
17
+
18
+ define_action :get
19
+ define_action :post
20
+ define_action :put
21
+ define_action :delete
22
+ define_action :head
23
+ define_action :patch
24
+
25
+ def callback(*args, &block)
26
+ require 'webmock'
27
+ self.send(:include, WebMock::API)
28
+
29
+ options = if args.last.is_a?(Hash) then args.pop else {} end
30
+ options[:api_doc_dsl] = :callback
31
+ args.push(options)
32
+
33
+ context(*args, &block)
34
+ end
35
+
36
+ def parameter(name, description, options = {})
37
+ parameters.push(options.merge(:name => name.to_s, :description => description))
38
+ end
39
+
40
+ def header(name, value)
41
+ headers[name] = value
42
+ end
43
+
44
+ private
45
+ def parameters
46
+ metadata[:parameters] ||= []
47
+ if superclass_metadata && metadata[:parameters].equal?(superclass_metadata[:parameters])
48
+ metadata[:parameters] = Marshal.load(Marshal.dump(superclass_metadata[:parameters]))
49
+ end
50
+ metadata[:parameters]
51
+ end
52
+
53
+ def headers
54
+ metadata[:headers] ||= {}
55
+ if superclass_metadata && metadata[:headers].equal?(superclass_metadata[:headers])
56
+ metadata[:headers] = Marshal.load(Marshal.dump(superclass_metadata[:headers]))
57
+ end
58
+ metadata[:headers]
59
+ end
60
+
61
+ def parameter_keys
62
+ parameters.map { |param| param[:name] }
63
+ end
64
+ end
65
+
66
+ def app
67
+ RspecApiDocumentation.configuration.app
68
+ end
69
+
70
+ def client
71
+ @client ||= RspecApiDocumentation::RackTestClient.new(self)
72
+ end
73
+
74
+ def no_doc(&block)
75
+ requests = example.metadata[:requests]
76
+ example.metadata[:requests] = []
77
+
78
+ instance_eval &block
79
+
80
+ example.metadata[:requests] = requests
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,49 @@
1
+ module RspecApiDocumentation
2
+ class Example
3
+ attr_reader :example, :configuration
4
+
5
+ def initialize(example, configuration)
6
+ @example = example
7
+ @configuration = configuration
8
+ end
9
+
10
+ def method_missing(method_sym, *args, &block)
11
+ if example.metadata.has_key?(method_sym)
12
+ example.metadata[method_sym]
13
+ else
14
+ example.send(method_sym, *args, &block)
15
+ end
16
+ end
17
+
18
+ def respond_to?(method_sym, include_private = false)
19
+ super || example.metadata.has_key?(method_sym) || example.respond_to?(method_sym, include_private)
20
+ end
21
+
22
+ def http_method
23
+ metadata[:method].to_s.upcase
24
+ end
25
+
26
+ def should_document?
27
+ return false if pending? || !metadata[:resource_name] || !metadata[:document]
28
+ return false if (Array(metadata[:document]) & Array(configuration.exclusion_filter)).length > 0
29
+ return true if (Array(metadata[:document]) & Array(configuration.filter)).length > 0
30
+ return true if configuration.filter == :all
31
+ end
32
+
33
+ def public?
34
+ metadata[:public]
35
+ end
36
+
37
+ def has_parameters?
38
+ respond_to?(:parameters) && parameters.present?
39
+ end
40
+
41
+ def explanation
42
+ metadata[:explanation] || nil
43
+ end
44
+
45
+ def requests
46
+ metadata[:requests] || []
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,32 @@
1
+ module RspecApiDocumentation
2
+ module Headers
3
+ private
4
+
5
+ def env_to_headers(env)
6
+ headers = {}
7
+ env.each do |key, value|
8
+ # HTTP_ACCEPT_CHARSET => Accept-Charset
9
+ if key =~ /^(HTTP_|CONTENT_TYPE)/
10
+ header = key.gsub(/^HTTP_/, '').titleize.split.join("-")
11
+ headers[header] = value
12
+ end
13
+ end
14
+ headers
15
+ end
16
+
17
+ def headers_to_env(headers)
18
+ headers.inject({}) do |hsh, (k, v)|
19
+ new_key = k.upcase.gsub("-", "_")
20
+ new_key = "HTTP_#{new_key}" unless new_key == "CONTENT_TYPE"
21
+ hsh[new_key] = v
22
+ hsh
23
+ end
24
+ end
25
+
26
+ def format_headers(headers)
27
+ headers.map do |key, value|
28
+ "#{key}: #{value}"
29
+ end.join("\n")
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,7 @@
1
+ module RspecApiDocumentation
2
+ class Index
3
+ def examples
4
+ @examples ||= []
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,70 @@
1
+ begin
2
+ require "active_support/secure_random"
3
+ rescue LoadError
4
+ # ActiveSupport::SecureRandom not provided in activesupport >= 3.2
5
+ end
6
+ require "webmock"
7
+ require "rack/oauth2"
8
+
9
+ module RspecApiDocumentation
10
+ class OAuth2MACClient < ClientBase
11
+ include WebMock::API
12
+ attr_accessor :last_response, :last_request
13
+ private :last_response, :last_request
14
+
15
+ def request_headers
16
+ env_to_headers(last_request.env)
17
+ end
18
+
19
+ def response_headers
20
+ last_response.headers
21
+ end
22
+
23
+ def query_string
24
+ last_request.env["QUERY_STRING"]
25
+ end
26
+
27
+ def status
28
+ last_response.status
29
+ end
30
+
31
+ def response_body
32
+ last_response.body
33
+ end
34
+
35
+ def request_content_type
36
+ last_request.content_type
37
+ end
38
+
39
+ def response_content_type
40
+ last_response.content_type
41
+ end
42
+
43
+ protected
44
+
45
+ def do_request(method, path, params, request_headers)
46
+ self.last_response = access_token.send(method, "http://example.com#{path}", :body => params, :header => headers(method, path, params, request_headers))
47
+ end
48
+
49
+ class ProxyApp < Struct.new(:client, :app)
50
+ def call(env)
51
+ env["QUERY_STRING"] = query_string_hack(env)
52
+ client.last_request = Struct.new(:env, :content_type).new(env, env["CONTENT_TYPE"])
53
+ app.call(env.merge("SCRIPT_NAME" => ""))
54
+ end
55
+
56
+ private
57
+ def query_string_hack(env)
58
+ env["QUERY_STRING"].gsub('%5B', '[').gsub('%5D', ']').gsub(/\[\d+/) { |s| "#{$1}[" }
59
+ end
60
+ end
61
+
62
+ def access_token
63
+ @access_token ||= begin
64
+ app = ProxyApp.new(self, context.app)
65
+ stub_request(:any, %r{http://example\.com}).to_rack(app)
66
+ Rack::OAuth2::Client.new(options.merge(:host => "example.com", :scheme => "http")).access_token!
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,58 @@
1
+ module RspecApiDocumentation
2
+ class RackTestClient < ClientBase
3
+
4
+ delegate :last_request, :last_response, :to => :rack_test_session
5
+ private :last_request, :last_response
6
+
7
+ def request_headers
8
+ env_to_headers(last_request.env)
9
+ end
10
+
11
+ def response_headers
12
+ last_response.headers
13
+ end
14
+
15
+ def query_string
16
+ last_request.env["QUERY_STRING"]
17
+ end
18
+
19
+ def status
20
+ last_response.status
21
+ end
22
+
23
+ def response_body
24
+ last_response.body
25
+ end
26
+
27
+ def request_content_type
28
+ last_request.content_type
29
+ end
30
+
31
+ def response_content_type
32
+ last_response.content_type
33
+ end
34
+
35
+ protected
36
+
37
+ def do_request(method, path, params, request_headers)
38
+ rack_test_session.send(method, path, params, headers(method, path, params, request_headers))
39
+ end
40
+
41
+ def headers(*args)
42
+ headers_to_env(super)
43
+ end
44
+
45
+ private
46
+
47
+ def rack_test_session
48
+ @rack_test_session ||= Struct.new(:app) do
49
+ begin
50
+ require "rack/test"
51
+ include Rack::Test::Methods
52
+ rescue LoadError
53
+ raise "#{self.class.name} requires Rack::Test >= 0.5.5. Please add it to your test dependencies."
54
+ end
55
+ end.new(app)
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,7 @@
1
+ module RspecApiDocumentation
2
+ class Railtie < Rails::Railtie
3
+ rake_tasks do
4
+ load "tasks/docs.rake"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,31 @@
1
+ module RspecApiDocumentation
2
+ class TestServer < Struct.new(:context)
3
+ include Headers
4
+
5
+ delegate :example, :to => :context
6
+ delegate :metadata, :to => :example
7
+
8
+ attr_reader :request_method, :request_headers, :request_body
9
+
10
+ def call(env)
11
+ input = env["rack.input"]
12
+ input.rewind
13
+
14
+ @request_method = env["REQUEST_METHOD"]
15
+ @request_headers = env_to_headers(env)
16
+ @request_body = input.read
17
+
18
+ request_metadata = {}
19
+
20
+ request_metadata[:request_method] = @request_method
21
+ request_metadata[:request_path] = env["PATH_INFO"]
22
+ request_metadata[:request_body] = @request_body
23
+ request_metadata[:request_headers] = @request_headers
24
+
25
+ metadata[:requests] ||= []
26
+ metadata[:requests] << request_metadata
27
+
28
+ return [200, {}, [""]]
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,16 @@
1
+ module RspecApiDocumentation
2
+ module Views
3
+ class HtmlExample < MarkupExample
4
+ EXTENSION = 'html'
5
+
6
+ def initialize(example, configuration)
7
+ super
8
+ self.template_name = "rspec_api_documentation/html_example"
9
+ end
10
+
11
+ def extension
12
+ EXTENSION
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,14 @@
1
+ module RspecApiDocumentation
2
+ module Views
3
+ class HtmlIndex < MarkupIndex
4
+ def initialize(index, configuration)
5
+ super
6
+ self.template_name = "rspec_api_documentation/html_index"
7
+ end
8
+
9
+ def examples
10
+ @index.examples.map { |example| HtmlExample.new(example, @configuration) }
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,58 @@
1
+ require 'mustache'
2
+
3
+ module RspecApiDocumentation
4
+ module Views
5
+ class MarkupExample < Mustache
6
+ def initialize(example, configuration)
7
+ @example = example
8
+ @host = configuration.curl_host
9
+ self.template_path = configuration.template_path
10
+ end
11
+
12
+ def method_missing(method, *args, &block)
13
+ @example.send(method, *args, &block)
14
+ end
15
+
16
+ def respond_to?(method, include_private = false)
17
+ super || @example.respond_to?(method, include_private)
18
+ end
19
+
20
+ def dirname
21
+ resource_name.downcase.gsub(/\s+/, '_')
22
+ end
23
+
24
+ def filename
25
+ basename = description.downcase.gsub(/\s+/, '_').gsub(/[^a-z_]/, '')
26
+ basename = Digest::MD5.new.update(description).to_s if basename.blank?
27
+ "#{basename}.#{extension}"
28
+ end
29
+
30
+ def requests
31
+ super.map do |hash|
32
+ hash[:request_headers_text] = format_hash(hash[:request_headers])
33
+ hash[:request_query_parameters_text] = format_hash(hash[:request_query_parameters])
34
+ hash[:response_headers_text] = format_hash(hash[:response_headers])
35
+ if @host
36
+ hash[:curl] = hash[:curl].output(@host) if hash[:curl].is_a? RspecApiDocumentation::Curl
37
+ else
38
+ hash[:curl] = nil
39
+ end
40
+ hash
41
+ end
42
+ end
43
+
44
+ def extension
45
+ raise 'Parent class. This method should not be called.'
46
+ end
47
+
48
+ private
49
+
50
+ def format_hash(hash = {})
51
+ return nil unless hash.present?
52
+ hash.collect do |k, v|
53
+ "#{k}: #{v}"
54
+ end.join("\n")
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,21 @@
1
+ require 'mustache'
2
+
3
+ module RspecApiDocumentation
4
+ module Views
5
+ class MarkupIndex < Mustache
6
+ def initialize(index, configuration)
7
+ @index = index
8
+ @configuration = configuration
9
+ self.template_path = configuration.template_path
10
+ end
11
+
12
+ def api_name
13
+ @configuration.api_name
14
+ end
15
+
16
+ def sections
17
+ RspecApiDocumentation::Writers::IndexWriter.sections(examples, @configuration)
18
+ end
19
+ end
20
+ end
21
+ end