rspec-api-documentation 1.1.1.alpha
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.
- 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
|