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,27 @@
|
|
1
|
+
module RspecApiDocs
|
2
|
+
class << self
|
3
|
+
attr_accessor :configuration
|
4
|
+
|
5
|
+
def configuration
|
6
|
+
@configuration ||= Config.new
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.configure
|
11
|
+
self.configuration ||= Config.new
|
12
|
+
yield configuration
|
13
|
+
end
|
14
|
+
|
15
|
+
class Config
|
16
|
+
attr_accessor \
|
17
|
+
:output_dir,
|
18
|
+
:renderer,
|
19
|
+
:validate_params
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
@output_dir = 'docs'
|
23
|
+
@renderer = :json
|
24
|
+
@validate_params = true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/rspec_api_docs/dsl.rb
CHANGED
@@ -3,19 +3,44 @@ require 'rspec_api_docs/dsl/request_store'
|
|
3
3
|
require 'rspec_api_docs/dsl/doc_proxy'
|
4
4
|
|
5
5
|
module RspecApiDocs
|
6
|
+
# This module is intended to be included in your RSpec specs to expose the
|
7
|
+
# {#doc} method.
|
6
8
|
module Dsl
|
7
|
-
|
8
|
-
|
9
|
+
# DSL method for use in your RSpec examples.
|
10
|
+
#
|
11
|
+
# Usage:
|
12
|
+
#
|
13
|
+
# it 'returns a character' do
|
14
|
+
# doc do
|
15
|
+
# title 'Returns a Character'
|
16
|
+
# description 'Allows you to return a single character.'
|
17
|
+
# path '/characters/:id'
|
18
|
+
#
|
19
|
+
# param :id, 'The id of a character', required: true
|
20
|
+
#
|
21
|
+
# field :id, 'The id of a character', scope: :character
|
22
|
+
# field :name, "The character's name", scope: :character
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# get '/characters/1'
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# For more info on the methods available in the block, see {DocProxy}.
|
29
|
+
#
|
30
|
+
# @param should_document [true, false] clear documentation metadata for the example
|
31
|
+
# @return [RequestStore, nil] an object to store request/response pairs
|
32
|
+
def doc(should_document = true, &block)
|
33
|
+
if should_document
|
34
|
+
example.metadata[METADATA_NAMESPACE] ||= {}
|
9
35
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
RequestStore.new(example)
|
15
|
-
end
|
36
|
+
if block
|
37
|
+
DocProxy.new(example).instance_eval(&block)
|
38
|
+
end
|
16
39
|
|
17
|
-
|
18
|
-
|
40
|
+
RequestStore.new(example)
|
41
|
+
else
|
42
|
+
example.metadata[METADATA_NAMESPACE] = nil
|
43
|
+
end
|
19
44
|
end
|
20
45
|
|
21
46
|
private
|
@@ -7,22 +7,74 @@ module RspecApiDocs
|
|
7
7
|
@metadata = example.metadata
|
8
8
|
end
|
9
9
|
|
10
|
+
# For setting the name of the example.
|
11
|
+
#
|
12
|
+
# E.g. "Returns a Character"
|
13
|
+
#
|
14
|
+
# @return [void]
|
10
15
|
def name(value)
|
11
16
|
metadata[METADATA_NAMESPACE][:example_name] = value
|
12
17
|
end
|
13
18
|
|
19
|
+
# For setting the name of the resource.
|
20
|
+
#
|
21
|
+
# E.g. "Characters"
|
22
|
+
#
|
23
|
+
# @return [void]
|
14
24
|
def resource_name(value)
|
15
25
|
metadata[METADATA_NAMESPACE][:resource_name] = value
|
16
26
|
end
|
17
27
|
|
28
|
+
# For setting the description of the resource.
|
29
|
+
#
|
30
|
+
# E.g. "Orders can be created, viewed, and deleted"
|
31
|
+
#
|
32
|
+
# @return [void]
|
33
|
+
def resource_description(value)
|
34
|
+
metadata[METADATA_NAMESPACE][:resource_description] = value
|
35
|
+
end
|
36
|
+
|
37
|
+
# For setting a description of the example.
|
38
|
+
#
|
39
|
+
# E.g. "Allows you to return a single character."
|
40
|
+
#
|
41
|
+
# @return [void]
|
18
42
|
def description(value)
|
19
43
|
metadata[METADATA_NAMESPACE][:description] = value
|
20
44
|
end
|
21
45
|
|
46
|
+
# For setting the request path of the example.
|
47
|
+
#
|
48
|
+
# E.g. "/characters/:id"
|
49
|
+
#
|
50
|
+
# @return [void]
|
22
51
|
def path(value)
|
23
52
|
metadata[METADATA_NAMESPACE][:path] = value
|
24
53
|
end
|
25
54
|
|
55
|
+
# For setting response fields of a request.
|
56
|
+
#
|
57
|
+
# Usage:
|
58
|
+
#
|
59
|
+
# doc do
|
60
|
+
# field :id, 'The id of a character', scope: :character
|
61
|
+
# field :name, "The character's name", scope: :character
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
# For a response body of:
|
65
|
+
#
|
66
|
+
# {
|
67
|
+
# character: {
|
68
|
+
# id: 1,
|
69
|
+
# name: 'Finn The Human'
|
70
|
+
# }
|
71
|
+
# }
|
72
|
+
#
|
73
|
+
# @param name [Symbol] the name of the response field
|
74
|
+
# @param description [String] a description of the response field
|
75
|
+
# @param scope [Symbol, Array<Symbol>] how the field is scoped
|
76
|
+
# @param type [String]
|
77
|
+
# @return [void]
|
26
78
|
def field(name, description, scope: [], type: nil)
|
27
79
|
metadata[METADATA_NAMESPACE][:fields] ||= {}
|
28
80
|
metadata[METADATA_NAMESPACE][:fields][name] = {
|
@@ -32,6 +84,25 @@ module RspecApiDocs
|
|
32
84
|
}
|
33
85
|
end
|
34
86
|
|
87
|
+
# For setting params of a request.
|
88
|
+
#
|
89
|
+
# Usage:
|
90
|
+
#
|
91
|
+
# doc do
|
92
|
+
# param :id, 'The id of a character', required: true
|
93
|
+
# param :name, 'A tag on a character', scope: :tag
|
94
|
+
# end
|
95
|
+
#
|
96
|
+
# For a path of:
|
97
|
+
#
|
98
|
+
# /characters/:id?tag[name]=:name
|
99
|
+
#
|
100
|
+
# @param name [Symbol] the name of the parameter
|
101
|
+
# @param description [String] a description of the parameter
|
102
|
+
# @param scope [Symbol, Array<Symbol>] how the parameter is scoped
|
103
|
+
# @param type [String]
|
104
|
+
# @param required [true, false] if the field is required
|
105
|
+
# @return [void]
|
35
106
|
def param(name, description, scope: [], type: nil, required: false)
|
36
107
|
metadata[METADATA_NAMESPACE][:parameters] ||= {}
|
37
108
|
metadata[METADATA_NAMESPACE][:parameters][name] = {
|
@@ -1,5 +1,6 @@
|
|
1
1
|
module RspecApiDocs
|
2
2
|
module Dsl
|
3
|
+
# Used to store request/response pairs.
|
3
4
|
class RequestStore
|
4
5
|
attr_reader :metadata
|
5
6
|
|
@@ -7,6 +8,26 @@ module RspecApiDocs
|
|
7
8
|
@metadata = example.metadata
|
8
9
|
end
|
9
10
|
|
11
|
+
# Only needed if you need to store multiple requests for a single example.
|
12
|
+
#
|
13
|
+
# Usage:
|
14
|
+
#
|
15
|
+
# it 'stores the requests a character' do
|
16
|
+
# doc do
|
17
|
+
# explanation 'Creating and requesting a character'
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# post '/characters', {name: 'Finn The Human'}
|
21
|
+
#
|
22
|
+
# doc << [last_request, last_response]
|
23
|
+
#
|
24
|
+
# get '/characters/1'
|
25
|
+
#
|
26
|
+
# # The last request/response pair is stored automatically
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# @param value [Array<Rack::Request, Rack::Response>] an array of a request and response object
|
30
|
+
# @return [void]
|
10
31
|
def <<(value)
|
11
32
|
metadata[METADATA_NAMESPACE][:requests] ||= []
|
12
33
|
metadata[METADATA_NAMESPACE][:requests] << value.sort_by { |v| v.class.name }.reverse
|
@@ -1,27 +1,65 @@
|
|
1
1
|
require 'rspec/core/formatters/base_formatter'
|
2
|
-
require 'json'
|
3
2
|
|
3
|
+
require 'rspec_api_docs'
|
4
4
|
require 'rspec_api_docs/formatter/resource'
|
5
|
-
require 'rspec_api_docs/formatter/
|
5
|
+
require 'rspec_api_docs/formatter/renderer/json_renderer'
|
6
|
+
require 'rspec_api_docs/formatter/renderer/raddocs_renderer'
|
7
|
+
require 'rspec_api_docs/formatter/renderer/slate_renderer'
|
6
8
|
|
7
9
|
module RspecApiDocs
|
10
|
+
# Unknown renderer configured.
|
11
|
+
UnknownRenderer = Class.new(BaseError)
|
12
|
+
|
13
|
+
# The RSpec formatter.
|
14
|
+
#
|
15
|
+
# Usage:
|
16
|
+
#
|
17
|
+
# rspec --format=RspecApiDocs::Formatter
|
8
18
|
class Formatter < RSpec::Core::Formatters::BaseFormatter
|
9
19
|
RSpec::Core::Formatters.register self, :example_passed, :close
|
10
20
|
|
11
21
|
attr_reader :resources
|
12
22
|
|
13
23
|
def initialize(*args)
|
14
|
-
@resources =
|
24
|
+
@resources = {}
|
15
25
|
super args
|
16
26
|
end
|
17
27
|
|
28
|
+
# Initializes and stores {Resource}s.
|
29
|
+
#
|
30
|
+
# @return [void]
|
18
31
|
def example_passed(example_notification)
|
19
|
-
|
20
|
-
|
32
|
+
rspec_example = example_notification.example
|
33
|
+
return unless rspec_example.metadata[METADATA_NAMESPACE]
|
34
|
+
resource = Resource.new(rspec_example)
|
35
|
+
resources[resource.name] ||= resource
|
36
|
+
resources[resource.name].examples << Resource::Example.new(rspec_example)
|
21
37
|
end
|
22
38
|
|
39
|
+
# Calls the configured renderer with the stored {Resource}s.
|
40
|
+
#
|
41
|
+
# @return [void]
|
23
42
|
def close(null_notification)
|
24
|
-
|
43
|
+
renderer.new(resources.values).render
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def renderer
|
49
|
+
value = RspecApiDocs.configuration.renderer
|
50
|
+
|
51
|
+
case value
|
52
|
+
when :json
|
53
|
+
Renderer::JSONRenderer
|
54
|
+
when :raddocs
|
55
|
+
Renderer::RaddocsRenderer
|
56
|
+
when :slate
|
57
|
+
Renderer::SlateRenderer
|
58
|
+
when Class
|
59
|
+
value
|
60
|
+
else
|
61
|
+
raise UnknownRenderer, "unknown renderer #{value.inspect}"
|
62
|
+
end
|
25
63
|
end
|
26
64
|
end
|
27
65
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'rspec_api_docs/formatter/renderer/json_renderer/name'
|
3
|
+
require 'rspec_api_docs/formatter/renderer/json_renderer/resource_serializer'
|
4
|
+
|
5
|
+
module RspecApiDocs
|
6
|
+
module Renderer
|
7
|
+
class JSONRenderer
|
8
|
+
attr_reader :resources
|
9
|
+
|
10
|
+
def initialize(resources)
|
11
|
+
@resources = resources
|
12
|
+
end
|
13
|
+
|
14
|
+
def render
|
15
|
+
FileUtils.mkdir_p output_file.dirname
|
16
|
+
|
17
|
+
File.open(output_file, 'w') do |f|
|
18
|
+
f.write JSON.pretty_generate(output) + "\n"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def output
|
25
|
+
resources.map do |resource|
|
26
|
+
ResourceSerializer.new(resource).to_h
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def output_file
|
31
|
+
Pathname.new(RspecApiDocs.configuration.output_dir) + 'index.json'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module RspecApiDocs
|
2
|
+
module Renderer
|
3
|
+
class JSONRenderer
|
4
|
+
class ExampleSerializer
|
5
|
+
attr_reader :example
|
6
|
+
|
7
|
+
def initialize(example)
|
8
|
+
@example = example
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_h # rubocop:disable Metrics/AbcSize
|
12
|
+
{
|
13
|
+
description: example.description,
|
14
|
+
name: example.name,
|
15
|
+
httpMethod: example.http_method,
|
16
|
+
parameters: parameters(example.parameters),
|
17
|
+
path: example.path,
|
18
|
+
requests: example.requests,
|
19
|
+
responseFields: response_fields(example.response_fields),
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def parameters(parameters)
|
26
|
+
parameters.map do |parameter|
|
27
|
+
{
|
28
|
+
name: Name.(name: parameter.name, scope: parameter.scope),
|
29
|
+
description: parameter.description,
|
30
|
+
required: parameter.required,
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def response_fields(fields)
|
36
|
+
fields.map do |field|
|
37
|
+
{
|
38
|
+
name: Name.(name: field.name, scope: field.scope),
|
39
|
+
description: field.description,
|
40
|
+
type: field.type,
|
41
|
+
}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module RspecApiDocs
|
2
|
+
module Renderer
|
3
|
+
class JSONRenderer
|
4
|
+
class Name
|
5
|
+
def self.call(name:, scope:)
|
6
|
+
scope = Array(scope)
|
7
|
+
if scope.empty?
|
8
|
+
name
|
9
|
+
else
|
10
|
+
scope.each_with_index.inject('') do |str, (part, index)|
|
11
|
+
str << (index == 0 ? part : "[#{part}]").to_s
|
12
|
+
end + "[#{name}]"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rspec_api_docs/formatter/renderer/json_renderer/example_serializer'
|
2
|
+
|
3
|
+
module RspecApiDocs
|
4
|
+
module Renderer
|
5
|
+
class JSONRenderer
|
6
|
+
class ResourceSerializer
|
7
|
+
attr_reader :resource
|
8
|
+
|
9
|
+
def initialize(resource)
|
10
|
+
@resource = resource
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_h
|
14
|
+
{
|
15
|
+
name: resource.name,
|
16
|
+
description: resource.description,
|
17
|
+
examples: examples,
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def examples
|
24
|
+
resource.examples.map do |example|
|
25
|
+
ExampleSerializer.new(example).to_h
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
require 'rspec_api_docs/formatter/renderer/raddocs_renderer/index_serializer'
|
4
|
+
require 'rspec_api_docs/formatter/renderer/raddocs_renderer/link'
|
5
|
+
require 'rspec_api_docs/formatter/renderer/raddocs_renderer/resource_serializer'
|
6
|
+
|
7
|
+
module RspecApiDocs
|
8
|
+
module Renderer
|
9
|
+
class RaddocsRenderer
|
10
|
+
attr_reader :resources
|
11
|
+
|
12
|
+
def initialize(resources)
|
13
|
+
@resources = resources
|
14
|
+
end
|
15
|
+
|
16
|
+
def render
|
17
|
+
write_index
|
18
|
+
write_examples
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def write_index
|
24
|
+
FileUtils.mkdir_p output_dir
|
25
|
+
|
26
|
+
File.open(output_dir + 'index.json', 'w') do |f|
|
27
|
+
f.write JSON.pretty_generate(IndexSerializer.new(resources).to_h) + "\n"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def write_examples
|
32
|
+
resources.each do |resource|
|
33
|
+
resource.examples.each do |example|
|
34
|
+
write_example(resource, example)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def write_example(resource, example)
|
40
|
+
FileUtils.mkdir_p file(resource, example).dirname
|
41
|
+
|
42
|
+
File.open(file(resource, example), 'w') do |f|
|
43
|
+
f.write JSON.pretty_generate(ResourceSerializer.new(resource, example).to_h) + "\n"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def output_dir
|
48
|
+
Pathname.new RspecApiDocs.configuration.output_dir
|
49
|
+
end
|
50
|
+
|
51
|
+
def file(resource, example)
|
52
|
+
output_dir + Link.(resource.name, example.name)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|