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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.rubocop.yml +55 -0
  4. data/README.md +222 -4
  5. data/Rakefile +9 -2
  6. data/bin/generate_integration_docs +14 -0
  7. data/lib/rspec_api_docs.rb +2 -16
  8. data/lib/rspec_api_docs/after.rb +20 -3
  9. data/lib/rspec_api_docs/after/type_checker.rb +56 -0
  10. data/lib/rspec_api_docs/config.rb +27 -0
  11. data/lib/rspec_api_docs/dsl.rb +35 -10
  12. data/lib/rspec_api_docs/dsl/doc_proxy.rb +71 -0
  13. data/lib/rspec_api_docs/dsl/request_store.rb +21 -0
  14. data/lib/rspec_api_docs/formatter.rb +44 -6
  15. data/lib/rspec_api_docs/formatter/renderer/json_renderer.rb +35 -0
  16. data/lib/rspec_api_docs/formatter/renderer/json_renderer/example_serializer.rb +47 -0
  17. data/lib/rspec_api_docs/formatter/renderer/json_renderer/name.rb +18 -0
  18. data/lib/rspec_api_docs/formatter/renderer/json_renderer/resource_serializer.rb +31 -0
  19. data/lib/rspec_api_docs/formatter/renderer/raddocs_renderer.rb +56 -0
  20. data/lib/rspec_api_docs/formatter/renderer/raddocs_renderer/index_serializer.rb +57 -0
  21. data/lib/rspec_api_docs/formatter/renderer/raddocs_renderer/link.rb +11 -0
  22. data/lib/rspec_api_docs/formatter/renderer/raddocs_renderer/resource_serializer.rb +58 -0
  23. data/lib/rspec_api_docs/formatter/renderer/slate_renderer.rb +29 -0
  24. data/lib/rspec_api_docs/formatter/renderer/slate_renderer/slate_index.html.md.erb +45 -0
  25. data/lib/rspec_api_docs/formatter/resource.rb +25 -111
  26. data/lib/rspec_api_docs/formatter/resource/example.rb +125 -0
  27. data/lib/rspec_api_docs/formatter/resource/parameter.rb +39 -0
  28. data/lib/rspec_api_docs/formatter/resource/response_field.rb +39 -0
  29. data/lib/rspec_api_docs/version.rb +1 -1
  30. data/rspec-api-docs.gemspec +1 -0
  31. metadata +34 -5
  32. data/lib/rspec_api_docs/formatter/index_serializer.rb +0 -37
  33. data/lib/rspec_api_docs/formatter/renderers/json_renderer.rb +0 -34
  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,11 @@
1
+ module RspecApiDocs
2
+ module Renderer
3
+ class RaddocsRenderer
4
+ class Link
5
+ def self.call(resource_name, example_name)
6
+ "#{resource_name.parameterize.underscore}/#{example_name.parameterize.underscore}.json"
7
+ end
8
+ end
9
+ end
10
+ end
11
+ 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 :example
8
+ attr_reader :rspec_example
6
9
 
7
- def initialize(example)
8
- @example = example
9
- end
10
+ # Returns an array of {Example}s
11
+ #
12
+ # @return [Array<Example>]
13
+ attr_accessor :examples
10
14
 
11
- def name
12
- metadata.fetch(:resource_name, example.metadata[:example_group][:description])
15
+ def initialize(rspec_example)
16
+ @rspec_example = rspec_example
17
+ @examples = []
13
18
  end
14
19
 
15
- def example_name
16
- metadata.fetch(:example_name, example.description)
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[:description]
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
- example.metadata[METADATA_NAMESPACE]
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