rspec-api-docs 0.1.0 → 0.2.0
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 +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
|