rspec-api-docs 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.rubocop.yml +55 -0
- data/README.md +222 -4
- data/Rakefile +9 -2
- data/bin/generate_integration_docs +14 -0
- data/lib/rspec_api_docs.rb +2 -16
- data/lib/rspec_api_docs/after.rb +20 -3
- data/lib/rspec_api_docs/after/type_checker.rb +56 -0
- data/lib/rspec_api_docs/config.rb +27 -0
- data/lib/rspec_api_docs/dsl.rb +35 -10
- data/lib/rspec_api_docs/dsl/doc_proxy.rb +71 -0
- data/lib/rspec_api_docs/dsl/request_store.rb +21 -0
- data/lib/rspec_api_docs/formatter.rb +44 -6
- data/lib/rspec_api_docs/formatter/renderer/json_renderer.rb +35 -0
- data/lib/rspec_api_docs/formatter/renderer/json_renderer/example_serializer.rb +47 -0
- data/lib/rspec_api_docs/formatter/renderer/json_renderer/name.rb +18 -0
- data/lib/rspec_api_docs/formatter/renderer/json_renderer/resource_serializer.rb +31 -0
- data/lib/rspec_api_docs/formatter/renderer/raddocs_renderer.rb +56 -0
- data/lib/rspec_api_docs/formatter/renderer/raddocs_renderer/index_serializer.rb +57 -0
- data/lib/rspec_api_docs/formatter/renderer/raddocs_renderer/link.rb +11 -0
- data/lib/rspec_api_docs/formatter/renderer/raddocs_renderer/resource_serializer.rb +58 -0
- data/lib/rspec_api_docs/formatter/renderer/slate_renderer.rb +29 -0
- data/lib/rspec_api_docs/formatter/renderer/slate_renderer/slate_index.html.md.erb +45 -0
- data/lib/rspec_api_docs/formatter/resource.rb +25 -111
- data/lib/rspec_api_docs/formatter/resource/example.rb +125 -0
- data/lib/rspec_api_docs/formatter/resource/parameter.rb +39 -0
- data/lib/rspec_api_docs/formatter/resource/response_field.rb +39 -0
- data/lib/rspec_api_docs/version.rb +1 -1
- data/rspec-api-docs.gemspec +1 -0
- metadata +34 -5
- data/lib/rspec_api_docs/formatter/index_serializer.rb +0 -37
- data/lib/rspec_api_docs/formatter/renderers/json_renderer.rb +0 -34
- data/lib/rspec_api_docs/formatter/resource_serializer.rb +0 -21
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'rspec_api_docs/formatter/renderer/raddocs_renderer/link'
|
2
|
+
require 'rspec_api_docs/formatter/resource'
|
3
|
+
|
4
|
+
module RspecApiDocs
|
5
|
+
module Renderer
|
6
|
+
class RaddocsRenderer
|
7
|
+
class IndexSerializer
|
8
|
+
class ExampleSerializer
|
9
|
+
attr_reader :example, :resource_name
|
10
|
+
|
11
|
+
def initialize(example, resource_name)
|
12
|
+
@example = example
|
13
|
+
@resource_name = resource_name
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_h
|
17
|
+
{
|
18
|
+
description: example.name,
|
19
|
+
link: link,
|
20
|
+
groups: groups,
|
21
|
+
route: example.path,
|
22
|
+
method: example.http_method.downcase,
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def link
|
29
|
+
Link.(resource_name, example.name)
|
30
|
+
end
|
31
|
+
|
32
|
+
def groups
|
33
|
+
'all'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
attr_reader :resources
|
38
|
+
|
39
|
+
def initialize(resources)
|
40
|
+
@resources = resources
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_h
|
44
|
+
{
|
45
|
+
resources: resources.map do |resource|
|
46
|
+
{
|
47
|
+
name: resource.name,
|
48
|
+
explanation: nil,
|
49
|
+
examples: resource.examples.map { |example| ExampleSerializer.new(example, resource.name).to_h },
|
50
|
+
}
|
51
|
+
end,
|
52
|
+
}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module RspecApiDocs
|
2
|
+
module Renderer
|
3
|
+
class RaddocsRenderer
|
4
|
+
class ResourceSerializer
|
5
|
+
attr_reader :resource, :example
|
6
|
+
|
7
|
+
def initialize(resource, example)
|
8
|
+
@resource = resource
|
9
|
+
@example = example
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_h # rubocop:disable Metrics/AbcSize
|
13
|
+
{
|
14
|
+
resource: resource.name,
|
15
|
+
resource_explanation: nil,
|
16
|
+
http_method: example.http_method,
|
17
|
+
route: example.path,
|
18
|
+
description: example.name,
|
19
|
+
explanation: example.description,
|
20
|
+
parameters: parameters(example.parameters),
|
21
|
+
response_fields: response_fields(example.response_fields),
|
22
|
+
requests: requests,
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def requests
|
29
|
+
example.requests.map { |request| request.merge(curl: nil) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def parameters(parameters)
|
33
|
+
parameters.map do |parameter|
|
34
|
+
result = {}
|
35
|
+
result[:required] = true if parameter.required
|
36
|
+
result[:scope] = parameter.scope
|
37
|
+
result = result.merge(
|
38
|
+
name: parameter.name,
|
39
|
+
description: parameter.description,
|
40
|
+
)
|
41
|
+
result
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def response_fields(fields)
|
46
|
+
fields.map do |field|
|
47
|
+
{
|
48
|
+
scope: field.scope,
|
49
|
+
Type: field.type,
|
50
|
+
name: field.name,
|
51
|
+
description: field.description,
|
52
|
+
}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module RspecApiDocs
|
2
|
+
module Renderer
|
3
|
+
class SlateRenderer
|
4
|
+
attr_reader :resources
|
5
|
+
|
6
|
+
def initialize(resources)
|
7
|
+
@resources = resources
|
8
|
+
end
|
9
|
+
|
10
|
+
def render
|
11
|
+
FileUtils.mkdir_p output_file.dirname
|
12
|
+
|
13
|
+
File.open(output_file, 'w') do |f|
|
14
|
+
f.write ERB.new(File.read(template), nil, '-').result(binding)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def output_file
|
21
|
+
Pathname.new(RspecApiDocs.configuration.output_dir) + 'index.html.md'
|
22
|
+
end
|
23
|
+
|
24
|
+
def template
|
25
|
+
File.expand_path('../slate_renderer/slate_index.html.md.erb', __FILE__)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
---
|
2
|
+
title: API Reference
|
3
|
+
search: true
|
4
|
+
---
|
5
|
+
|
6
|
+
<% resources.each do |resource| %>
|
7
|
+
# <%= resource.name %>
|
8
|
+
|
9
|
+
<% resource.examples.each do |example| %>
|
10
|
+
## <%= example.name %>
|
11
|
+
|
12
|
+
<%= example.description %>
|
13
|
+
|
14
|
+
<% example.requests.each do |request| -%>
|
15
|
+
```json
|
16
|
+
<% response_body = JSON.parse(request[:response_body]) if request[:response_body] %>
|
17
|
+
<%= JSON.pretty_generate(response_body) if response_body %>
|
18
|
+
```
|
19
|
+
|
20
|
+
### HTTP Request
|
21
|
+
|
22
|
+
`<%= request[:request_method] %> http://example.com<%= request[:request_path] %>`
|
23
|
+
|
24
|
+
<% end -%>
|
25
|
+
<% unless example.parameters.empty? -%>
|
26
|
+
### Query Parameters
|
27
|
+
|
28
|
+
Parameter | Required | Description
|
29
|
+
--------- | ------- | -----------
|
30
|
+
<% example.parameters.each do |parameter| -%>
|
31
|
+
<%= parameter.name %> | <%= !!parameter.required %> | <%= parameter.description %>
|
32
|
+
<% end -%>
|
33
|
+
<% end -%>
|
34
|
+
|
35
|
+
<% unless example.response_fields.empty? -%>
|
36
|
+
### Response Fields
|
37
|
+
|
38
|
+
Parameter | Type | Description
|
39
|
+
--------- | ------- | -----------
|
40
|
+
<% example.response_fields.each do |parameter| -%>
|
41
|
+
<%= parameter.name %> | <%= parameter.type %> | <%= parameter.description %>
|
42
|
+
<% end -%>
|
43
|
+
<% end -%>
|
44
|
+
<% end -%>
|
45
|
+
<% end -%>
|
@@ -1,130 +1,44 @@
|
|
1
1
|
require 'active_support/inflector'
|
2
|
+
require 'rspec_api_docs/formatter/resource/example'
|
3
|
+
require 'rspec_api_docs/formatter/resource/parameter'
|
4
|
+
require 'rspec_api_docs/formatter/resource/response_field'
|
2
5
|
|
3
6
|
module RspecApiDocs
|
4
7
|
class Resource
|
5
|
-
attr_reader :
|
8
|
+
attr_reader :rspec_example
|
6
9
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
+
# Returns an array of {Example}s
|
11
|
+
#
|
12
|
+
# @return [Array<Example>]
|
13
|
+
attr_accessor :examples
|
10
14
|
|
11
|
-
def
|
12
|
-
|
15
|
+
def initialize(rspec_example)
|
16
|
+
@rspec_example = rspec_example
|
17
|
+
@examples = []
|
13
18
|
end
|
14
19
|
|
15
|
-
|
16
|
-
|
20
|
+
# The name of the resource.
|
21
|
+
#
|
22
|
+
# E.g. "Characters"
|
23
|
+
#
|
24
|
+
# @return [String]
|
25
|
+
def name
|
26
|
+
metadata.fetch(:resource_name, rspec_example.metadata[:example_group][:description])
|
17
27
|
end
|
18
28
|
|
29
|
+
# The description of the resource.
|
30
|
+
#
|
31
|
+
# E.g. "Orders can be created, viewed, and deleted"
|
32
|
+
#
|
33
|
+
# @return [String]
|
19
34
|
def description
|
20
|
-
metadata[:
|
21
|
-
end
|
22
|
-
|
23
|
-
def parameters
|
24
|
-
metadata.fetch(:parameters, []).map do |name, field|
|
25
|
-
result = {}
|
26
|
-
result[:required] = true if field[:required]
|
27
|
-
result[:scope] = field[:scope].join unless field[:scope].empty?
|
28
|
-
result = result.merge(
|
29
|
-
name: name,
|
30
|
-
description: field[:description],
|
31
|
-
)
|
32
|
-
result
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def response_fields
|
37
|
-
metadata.fetch(:fields, []).map do |name, field|
|
38
|
-
{
|
39
|
-
scope: field[:scope].join,
|
40
|
-
Type: field[:type],
|
41
|
-
name: name,
|
42
|
-
description: field[:description],
|
43
|
-
}
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def requests
|
48
|
-
reqs = metadata.fetch(:requests, []).reject { |x| x.any?(&:nil?) }
|
49
|
-
reqs.map do |request, response|
|
50
|
-
{
|
51
|
-
request_method: request.request_method,
|
52
|
-
request_path: request_path(request),
|
53
|
-
request_body: request_body(request.body),
|
54
|
-
request_headers: request_headers(request.env),
|
55
|
-
request_query_parameters: request.params,
|
56
|
-
request_content_type: request.content_type,
|
57
|
-
response_status: response.status,
|
58
|
-
response_status_text: response_status_text(response.status),
|
59
|
-
response_body: response_body(response.body),
|
60
|
-
response_headers: response.headers,
|
61
|
-
response_content_type: response.content_type,
|
62
|
-
curl: curl,
|
63
|
-
}
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
# NOTE: returns the first route requested
|
68
|
-
def path
|
69
|
-
metadata.fetch(:path) do
|
70
|
-
reqs = metadata.fetch(:requests, []).reject { |x| x.any?(&:nil?) }
|
71
|
-
return if reqs.empty?
|
72
|
-
reqs.first.first.path
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
# NOTE: returns the first HTTP method used
|
77
|
-
def http_method
|
78
|
-
reqs = metadata.fetch(:requests, []).reject { |x| x.any?(&:nil?) }
|
79
|
-
return if reqs.empty?
|
80
|
-
reqs.first.first.request_method
|
81
|
-
end
|
82
|
-
|
83
|
-
def link
|
84
|
-
"#{name.downcase}/#{example_name.parameterize.underscore}.json"
|
85
|
-
end
|
86
|
-
|
87
|
-
def groups
|
88
|
-
'all'
|
35
|
+
metadata[:resource_description]
|
89
36
|
end
|
90
37
|
|
91
38
|
private
|
92
39
|
|
93
|
-
# http://stackoverflow.com/a/33235714/826820
|
94
|
-
def request_headers(env)
|
95
|
-
Hash[
|
96
|
-
*env.select { |k,v| k.start_with? 'HTTP_' }
|
97
|
-
.collect { |k,v| [k.sub(/^HTTP_/, ''), v] }
|
98
|
-
.collect { |k,v| [k.split('_').collect(&:capitalize).join('-'), v] }
|
99
|
-
.sort.flatten
|
100
|
-
]
|
101
|
-
end
|
102
|
-
|
103
|
-
def request_path(request)
|
104
|
-
URI(request.path).tap do |uri|
|
105
|
-
uri.query = request.query_string unless request.query_string.empty?
|
106
|
-
end.to_s
|
107
|
-
end
|
108
|
-
|
109
|
-
def request_body(body)
|
110
|
-
body = body.read
|
111
|
-
body.empty? ? nil : body
|
112
|
-
end
|
113
|
-
|
114
|
-
def response_body(body)
|
115
|
-
body.empty? ? nil : body
|
116
|
-
end
|
117
|
-
|
118
|
-
def response_status_text(status)
|
119
|
-
Rack::Utils::HTTP_STATUS_CODES[status]
|
120
|
-
end
|
121
|
-
|
122
|
-
# TODO
|
123
|
-
def curl
|
124
|
-
end
|
125
|
-
|
126
40
|
def metadata
|
127
|
-
|
41
|
+
rspec_example.metadata[METADATA_NAMESPACE]
|
128
42
|
end
|
129
43
|
end
|
130
44
|
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module RspecApiDocs
|
2
|
+
class Resource
|
3
|
+
class Example
|
4
|
+
attr_reader :example
|
5
|
+
|
6
|
+
def initialize(example)
|
7
|
+
@example = example
|
8
|
+
end
|
9
|
+
|
10
|
+
# The name of the example.
|
11
|
+
#
|
12
|
+
# E.g. "Returns a Character"
|
13
|
+
#
|
14
|
+
# @return [String]
|
15
|
+
def name
|
16
|
+
metadata.fetch(:example_name, example.description)
|
17
|
+
end
|
18
|
+
|
19
|
+
# The description of the example.
|
20
|
+
#
|
21
|
+
# E.g. "For getting information about a Character."
|
22
|
+
#
|
23
|
+
# @return [String]
|
24
|
+
def description
|
25
|
+
metadata[:description]
|
26
|
+
end
|
27
|
+
|
28
|
+
# Parameters for the example.
|
29
|
+
#
|
30
|
+
# @return [Array<Parameter>]
|
31
|
+
def parameters
|
32
|
+
metadata.fetch(:parameters, []).map do |name, parameter|
|
33
|
+
Parameter.new(name, parameter)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Response fields for the example.
|
38
|
+
#
|
39
|
+
# @return [Array<ResponseField>]
|
40
|
+
def response_fields
|
41
|
+
metadata.fetch(:fields, []).map do |name, field|
|
42
|
+
ResponseField.new(name, field)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Requests stored for the example.
|
47
|
+
#
|
48
|
+
# @return [Array<Hash>]
|
49
|
+
def requests # rubocop:disable Metrics/AbcSize
|
50
|
+
request_response_pairs.map do |request, response|
|
51
|
+
{
|
52
|
+
request_method: request.request_method,
|
53
|
+
request_path: request_path(request),
|
54
|
+
request_body: request_body(request.body),
|
55
|
+
request_headers: request_headers(request.env),
|
56
|
+
request_query_parameters: request.params,
|
57
|
+
request_content_type: request.content_type,
|
58
|
+
response_status: response.status,
|
59
|
+
response_status_text: response_status_text(response.status),
|
60
|
+
response_body: response_body(response.body),
|
61
|
+
response_headers: response.headers,
|
62
|
+
response_content_type: response.content_type,
|
63
|
+
}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Path stored on the example OR the path of first route requested.
|
68
|
+
#
|
69
|
+
# @return [String, nil]
|
70
|
+
def path
|
71
|
+
metadata.fetch(:path) do
|
72
|
+
return if request_response_pairs.empty?
|
73
|
+
request_response_pairs.first.first.path
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# The HTTP method of first route requested.
|
78
|
+
#
|
79
|
+
# @return [String, nil]
|
80
|
+
def http_method
|
81
|
+
return if request_response_pairs.empty?
|
82
|
+
request_response_pairs.first.first.request_method
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def request_response_pairs
|
88
|
+
metadata.fetch(:requests, []).reject { |pair| pair.any?(&:nil?) }
|
89
|
+
end
|
90
|
+
|
91
|
+
# http://stackoverflow.com/a/33235714/826820
|
92
|
+
def request_headers(env)
|
93
|
+
Hash[
|
94
|
+
*env.select { |k,v| k.start_with? 'HTTP_' }
|
95
|
+
.collect { |k,v| [k.sub(/^HTTP_/, ''), v] }
|
96
|
+
.collect { |k,v| [k.split('_').collect(&:capitalize).join('-'), v] }
|
97
|
+
.sort.flatten
|
98
|
+
]
|
99
|
+
end
|
100
|
+
|
101
|
+
def request_path(request)
|
102
|
+
URI(request.path).tap do |uri|
|
103
|
+
uri.query = request.query_string unless request.query_string.empty?
|
104
|
+
end.to_s
|
105
|
+
end
|
106
|
+
|
107
|
+
def request_body(body)
|
108
|
+
body = body.read
|
109
|
+
body.empty? ? nil : body
|
110
|
+
end
|
111
|
+
|
112
|
+
def response_body(body)
|
113
|
+
body.empty? ? nil : body
|
114
|
+
end
|
115
|
+
|
116
|
+
def response_status_text(status)
|
117
|
+
Rack::Utils::HTTP_STATUS_CODES[status]
|
118
|
+
end
|
119
|
+
|
120
|
+
def metadata
|
121
|
+
example.metadata[METADATA_NAMESPACE]
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|