rspec-api-documentation 1.1.1.alpha
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/rspec_api_documentation.rb +63 -0
- data/lib/rspec_api_documentation/api_documentation.rb +38 -0
- data/lib/rspec_api_documentation/api_formatter.rb +45 -0
- data/lib/rspec_api_documentation/client_base.rb +82 -0
- data/lib/rspec_api_documentation/configuration.rb +84 -0
- data/lib/rspec_api_documentation/curl.rb +61 -0
- data/lib/rspec_api_documentation/dsl.rb +17 -0
- data/lib/rspec_api_documentation/dsl/callback.rb +25 -0
- data/lib/rspec_api_documentation/dsl/endpoint.rb +134 -0
- data/lib/rspec_api_documentation/dsl/resource.rb +83 -0
- data/lib/rspec_api_documentation/example.rb +49 -0
- data/lib/rspec_api_documentation/headers.rb +32 -0
- data/lib/rspec_api_documentation/index.rb +7 -0
- data/lib/rspec_api_documentation/oauth2_mac_client.rb +70 -0
- data/lib/rspec_api_documentation/rack_test_client.rb +58 -0
- data/lib/rspec_api_documentation/railtie.rb +7 -0
- data/lib/rspec_api_documentation/test_server.rb +31 -0
- data/lib/rspec_api_documentation/views/html_example.rb +16 -0
- data/lib/rspec_api_documentation/views/html_index.rb +14 -0
- data/lib/rspec_api_documentation/views/markup_example.rb +58 -0
- data/lib/rspec_api_documentation/views/markup_index.rb +21 -0
- data/lib/rspec_api_documentation/views/textile_example.rb +16 -0
- data/lib/rspec_api_documentation/views/textile_index.rb +14 -0
- data/lib/rspec_api_documentation/writers/combined_json_writer.rb +20 -0
- data/lib/rspec_api_documentation/writers/combined_text_writer.rb +106 -0
- data/lib/rspec_api_documentation/writers/formatter.rb +11 -0
- data/lib/rspec_api_documentation/writers/general_markup_writer.rb +42 -0
- data/lib/rspec_api_documentation/writers/html_writer.rb +21 -0
- data/lib/rspec_api_documentation/writers/index_writer.rb +16 -0
- data/lib/rspec_api_documentation/writers/json_iodocs_writer.rb +111 -0
- data/lib/rspec_api_documentation/writers/json_writer.rb +111 -0
- data/lib/rspec_api_documentation/writers/textile_writer.rb +21 -0
- data/lib/tasks/docs.rake +7 -0
- data/templates/rspec_api_documentation/html_example.mustache +106 -0
- data/templates/rspec_api_documentation/html_index.mustache +34 -0
- data/templates/rspec_api_documentation/textile_example.mustache +68 -0
- data/templates/rspec_api_documentation/textile_index.mustache +10 -0
- 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,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,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
|