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,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
|