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,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
@@ -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
- def doc(&block)
8
- example.metadata[METADATA_NAMESPACE] ||= {}
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
- if block
11
- DocProxy.new(example).instance_eval(&block)
12
- end
13
-
14
- RequestStore.new(example)
15
- end
36
+ if block
37
+ DocProxy.new(example).instance_eval(&block)
38
+ end
16
39
 
17
- def no_doc
18
- example.metadata[METADATA_NAMESPACE] = nil
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/renderers/json_renderer'
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
- return unless example_notification.example.metadata[METADATA_NAMESPACE]
20
- resources << Resource.new(example_notification.example)
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
- JSONRender.new(resources).render
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