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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 60a330ec02c82ad9a129e508386c67e173c0cd81
|
4
|
+
data.tar.gz: fed6d9f849bf9e1cee2cb44f04abce403353a064
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bc1785537527f94c561951c4ab62c6a0d37da817c26be4f55f8acbdaba7fc9cf14b31046c088a509b170a3fc36bb7484aa5c8d8fb93b956f5e7dd451572387cc
|
7
|
+
data.tar.gz: a258eba2025273e8f43a710d2f813c61633b7284d3a47714ed98b415557d54bbd4c6e88c0a29df19cd06597ad835a91cd16d5cc19cd78f4e7a20aab7cb2e416a
|
data/.gitignore
CHANGED
data/.rubocop.yml
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 2.2
|
3
|
+
DisplayCopNames: true
|
4
|
+
DisabledByDefault: true
|
5
|
+
|
6
|
+
Metrics/LineLength:
|
7
|
+
Max: 120
|
8
|
+
|
9
|
+
Metrics/AbcSize:
|
10
|
+
Enabled: true
|
11
|
+
|
12
|
+
Style/DotPosition:
|
13
|
+
EnforcedStyle: leading
|
14
|
+
|
15
|
+
Style/AlignHash:
|
16
|
+
Enabled: true
|
17
|
+
|
18
|
+
Style/ExtraSpacing:
|
19
|
+
AllowForAlignment: false
|
20
|
+
|
21
|
+
Style/HashSyntax:
|
22
|
+
EnforcedStyle: ruby19
|
23
|
+
|
24
|
+
Style/PercentLiteralDelimiters:
|
25
|
+
PreferredDelimiters:
|
26
|
+
'%': '{}'
|
27
|
+
'%i': '[]'
|
28
|
+
'%q': '{}'
|
29
|
+
'%Q': '{}'
|
30
|
+
'%r': '{}'
|
31
|
+
'%s': '[]'
|
32
|
+
'%w': '[]'
|
33
|
+
'%W': '[]'
|
34
|
+
'%x': '[]'
|
35
|
+
|
36
|
+
Style/SpaceInsideHashLiteralBraces:
|
37
|
+
EnforcedStyle: no_space
|
38
|
+
|
39
|
+
Style/SpaceInsideBlockBraces:
|
40
|
+
EnforcedStyleForEmptyBraces: space
|
41
|
+
|
42
|
+
Style/StringLiterals:
|
43
|
+
EnforcedStyle: single_quotes
|
44
|
+
|
45
|
+
Style/TrailingCommaInLiteral:
|
46
|
+
EnforcedStyleForMultiline: comma
|
47
|
+
|
48
|
+
Style/TrailingCommaInArguments:
|
49
|
+
EnforcedStyleForMultiline: comma
|
50
|
+
|
51
|
+
Style/ClassAndModuleChildren:
|
52
|
+
EnforcedStyle: nested
|
53
|
+
|
54
|
+
Style/MultilineOperationIndentation:
|
55
|
+
EnforcedStyle: indented
|
data/README.md
CHANGED
@@ -1,4 +1,12 @@
|
|
1
|
-
|
1
|
+
<h1 align="center">rspec-api-docs</h1>
|
2
|
+
|
3
|
+
<p align="center">Generate API documentation using RSpec</p>
|
4
|
+
|
5
|
+
<p align="center">
|
6
|
+
<a href="https://travis-ci.org/twe4ked/rspec-api-docs"><img src="https://img.shields.io/travis/twe4ked/rspec-api-docs.svg?style=flat-square" /></a>
|
7
|
+
<a href="https://rubygems.org/gems/rspec-api-docs"><img src="https://img.shields.io/gem/v/rspec-api-docs.svg?style=flat-square" /></a>
|
8
|
+
<a href="http://www.rubydoc.info/github/twe4ked/rspec-api-docs/master"><img src="https://img.shields.io/badge/docs-master-lightgrey.svg?style=flat-square" /></a>
|
9
|
+
</p>
|
2
10
|
|
3
11
|
## Installation
|
4
12
|
|
@@ -18,13 +26,213 @@ Or install it yourself as:
|
|
18
26
|
|
19
27
|
## Usage
|
20
28
|
|
21
|
-
|
29
|
+
**rspec-api-docs** works in two stages. The first stage introduces a new DSL
|
30
|
+
method, `doc`, to include in your RSpec specs.
|
31
|
+
|
32
|
+
``` ruby
|
33
|
+
require 'rspec_api_docs/dsl'
|
34
|
+
|
35
|
+
RSpec.describe 'Characters' do
|
36
|
+
include RspecApiDocs::Dsl
|
37
|
+
|
38
|
+
# ...
|
39
|
+
end
|
40
|
+
```
|
41
|
+
|
42
|
+
The `doc` method stores data in a hash on the RSpec example metadata.
|
43
|
+
|
44
|
+
The second stage is the formatter (`RspecApiDocs::Formatter`). The formatter
|
45
|
+
parses the hash stored on each RSpec example and uses a renderer
|
46
|
+
(lib/rspec_api_docs/formatter/renderer/) to write out your documentation.
|
47
|
+
|
48
|
+
```
|
49
|
+
bundle exec rspec spec/requests/characters_spec.rb --formatter=RspecApiDocs::Formatter
|
50
|
+
```
|
51
|
+
|
52
|
+
### DSL
|
53
|
+
|
54
|
+
First, require the DSL and include the DSL module.
|
55
|
+
|
56
|
+
You can do this in your `spec_helper.rb`:
|
57
|
+
|
58
|
+
``` ruby
|
59
|
+
require 'rspec_api_docs/dsl'
|
60
|
+
|
61
|
+
RSpec.configure do |config|
|
62
|
+
config.include RspecApiDocs::Dsl, type: :request
|
63
|
+
|
64
|
+
# ...
|
65
|
+
end
|
66
|
+
```
|
67
|
+
|
68
|
+
Or in individual specs:
|
69
|
+
|
70
|
+
|
71
|
+
``` ruby
|
72
|
+
require 'rspec_api_docs/dsl'
|
73
|
+
|
74
|
+
RSpec.describe 'Characters' do
|
75
|
+
include RspecApiDocs::Dsl
|
76
|
+
|
77
|
+
# ...
|
78
|
+
end
|
79
|
+
```
|
80
|
+
|
81
|
+
You also need to require a lambda that runs after each expectation:
|
82
|
+
|
83
|
+
``` ruby
|
84
|
+
require 'rspec_api_docs/after'
|
85
|
+
|
86
|
+
RSpec.configure do |config|
|
87
|
+
config.after &RspecApiDocs::After::Hook
|
88
|
+
end
|
89
|
+
```
|
90
|
+
|
91
|
+
This automatically stores the `last_request` and `last_response` objects for
|
92
|
+
use by the formatter.
|
93
|
+
|
94
|
+
**rspec-api-docs** doesn't touch any of the built-in RSpec DSL methods.
|
95
|
+
Everything is contained in the `doc` block.
|
96
|
+
|
97
|
+
You can use RSpec `before` blocks to share setup between multiple examples.
|
98
|
+
|
99
|
+
``` ruby
|
100
|
+
require 'rspec_api_docs/dsl'
|
101
|
+
|
102
|
+
RSpec.describe 'Characters' do
|
103
|
+
include RspecApiDocs::Dsl
|
104
|
+
|
105
|
+
before do
|
106
|
+
doc do
|
107
|
+
resource_name 'Characters'
|
108
|
+
resource_description <<-EOF
|
109
|
+
Characters inhabit the Land of Ooo.
|
110
|
+
EOF
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
describe 'GET /characters/:id' do
|
115
|
+
it 'returns a character' do
|
116
|
+
doc do
|
117
|
+
name 'Fetching a Character'
|
118
|
+
description 'For getting information about a Character.'
|
119
|
+
path '/characters/:id'
|
120
|
+
|
121
|
+
field :id, 'The id of a character', scope: :character, type: 'integer'
|
122
|
+
field :name, "The character's name", scope: :character, type: 'string'
|
123
|
+
end
|
124
|
+
|
125
|
+
get '/characters/1'
|
126
|
+
|
127
|
+
# normal expectations ...
|
128
|
+
end
|
129
|
+
|
130
|
+
# ...
|
131
|
+
end
|
132
|
+
end
|
133
|
+
```
|
134
|
+
|
135
|
+
#### `resource_name`
|
136
|
+
|
137
|
+
Accepts a string of the name of the resource.
|
22
138
|
|
139
|
+
> Characters
|
140
|
+
|
141
|
+
#### `resource_description`
|
142
|
+
|
143
|
+
Accepts a string that describes the resource.
|
144
|
+
|
145
|
+
> Characters inhabit the Land of Ooo.
|
146
|
+
|
147
|
+
#### `name`
|
148
|
+
|
149
|
+
Accepts a string of the name of the resource.
|
150
|
+
|
151
|
+
> Fetching a character
|
152
|
+
|
153
|
+
Note: This defaults to the "description" of the RSpec example.
|
154
|
+
|
155
|
+
|
156
|
+
``` ruby
|
157
|
+
it 'Fetching a character' do
|
158
|
+
# ...
|
159
|
+
end
|
160
|
+
```
|
161
|
+
|
162
|
+
#### `description`
|
163
|
+
|
164
|
+
Accepts a string that describes the example.
|
165
|
+
|
166
|
+
> To find out information about a Character.
|
167
|
+
|
168
|
+
#### `path`
|
169
|
+
|
170
|
+
Accepts a string for the path requested in the example.
|
171
|
+
|
172
|
+
> /characters/:id
|
173
|
+
|
174
|
+
Note: This defaults to the path of the first route requested in the example.
|
175
|
+
|
176
|
+
#### `field`
|
177
|
+
|
178
|
+
Accepts a `name`, `description`, and optionally a `scope` and `type`.
|
179
|
+
|
180
|
+
- `name` [`Symbol`] the name of the response field
|
181
|
+
- `description` [`String`] a description of the response field
|
182
|
+
- `scope` [`Symbol`, `Array<Symbol>`] _(optional)_ how the field is scoped
|
183
|
+
- `type` [`String`] _(optional)_ the type of the returned field
|
184
|
+
|
185
|
+
This can be called multiple times for each response field.
|
186
|
+
|
187
|
+
``` ruby
|
188
|
+
field :id, 'The id of a character', scope: :character, type: 'integer'
|
189
|
+
field :name, "The character's name", scope: :character, type: 'string'
|
23
190
|
```
|
24
|
-
|
25
|
-
|
191
|
+
|
192
|
+
#### `param`
|
193
|
+
|
194
|
+
Accepts a `name`, `description`, and optionally a `scope`, `type`, and `required` flag.
|
195
|
+
|
196
|
+
- `name` [`Symbol`] the name of the parameter
|
197
|
+
- `description` [`Symbol`] a description of the parameter
|
198
|
+
- `scope` [`Symbol`, `Array<Symbol>`] _(optional)_ how the parameter is scoped
|
199
|
+
- `type` [`String`] _(optional)_ the type of the parameter
|
200
|
+
- `required` [`Boolean`] _(optional)_ if the parameter is required
|
201
|
+
|
202
|
+
This can be called multiple times for each parameter.
|
203
|
+
|
204
|
+
``` ruby
|
205
|
+
param :id, 'The id of a character', scope: :character, type: 'integer', required: true
|
206
|
+
param :name, "The character's name", scope: :character, type: 'string'
|
26
207
|
```
|
27
208
|
|
209
|
+
See the integration specs for more examples of the DSL in use.
|
210
|
+
|
211
|
+
### Formatter
|
212
|
+
|
213
|
+
The formatter can be configured in your `spec_helper.rb`:
|
214
|
+
|
215
|
+
``` ruby
|
216
|
+
# Defaults are shown
|
217
|
+
|
218
|
+
RspecApiDocs.configure do |config|
|
219
|
+
# The output directory for file(s) created by the renderer.
|
220
|
+
config.output_dir = 'docs'
|
221
|
+
|
222
|
+
# One of :json, :raddocs, or :slate.
|
223
|
+
# This can also be a class that quacks like a renderer.
|
224
|
+
# A renderer is initialized with an array of `Resource`s and a `#render`
|
225
|
+
# method is called.
|
226
|
+
config.renderer = :json
|
227
|
+
|
228
|
+
# Set to false if you don't want to validate params are documented and their
|
229
|
+
# types in the after block.
|
230
|
+
config.validate_params = true
|
231
|
+
end
|
232
|
+
```
|
233
|
+
|
234
|
+
See [the documentation](http://www.rubydoc.info/github/twe4ked/rspec-api-docs/master).
|
235
|
+
|
28
236
|
## Development
|
29
237
|
|
30
238
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
@@ -35,6 +243,16 @@ release a new version, update the version number in `version.rb`, and then run
|
|
35
243
|
`bundle exec rake release`, which will create a git tag for the version, push
|
36
244
|
git commits and tags, and push the `.gem` file to [rubygems.org].
|
37
245
|
|
246
|
+
Regenerate this project's integration spec docs locally:
|
247
|
+
|
248
|
+
```
|
249
|
+
./bin/generate_integration_docs
|
250
|
+
```
|
251
|
+
|
252
|
+
## TODO
|
253
|
+
|
254
|
+
- Allow specifying an order (`precedence`?)
|
255
|
+
|
38
256
|
## Contributing
|
39
257
|
|
40
258
|
Bug reports and pull requests are welcome on GitHub at
|
data/Rakefile
CHANGED
@@ -1,6 +1,13 @@
|
|
1
1
|
require 'bundler/gem_tasks'
|
2
2
|
require 'rspec/core/rake_task'
|
3
|
+
require 'rubocop/rake_task'
|
3
4
|
|
4
|
-
RSpec::Core::RakeTask.new
|
5
|
+
RSpec::Core::RakeTask.new :rspec do |task|
|
6
|
+
task.verbose = false
|
7
|
+
end
|
5
8
|
|
6
|
-
|
9
|
+
RuboCop::RakeTask.new :rubocop do |task|
|
10
|
+
task.verbose = false
|
11
|
+
end
|
12
|
+
|
13
|
+
task default: [:rspec, :rubocop]
|
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env bash -ex
|
2
|
+
|
3
|
+
rm -rf spec/integration/output
|
4
|
+
|
5
|
+
bundle exec rspec spec/integration/*_spec.rb --format RspecApiDocs::Formatter --require ./spec/integration/json_helper.rb
|
6
|
+
bundle exec rspec spec/integration/*_spec.rb --format RspecApiDocs::Formatter --require ./spec/integration/raddocs_helper.rb
|
7
|
+
bundle exec rspec spec/integration/*_spec.rb --format RspecApiDocs::Formatter --require ./spec/integration/slate_helper.rb
|
8
|
+
|
9
|
+
{ set +x; } 2>/dev/null
|
10
|
+
|
11
|
+
if [[ -n "$(git status -z --porcelain spec/integration/output)" ]]; then
|
12
|
+
git diff spec/integration/output
|
13
|
+
exit 1
|
14
|
+
fi
|
data/lib/rspec_api_docs.rb
CHANGED
@@ -1,22 +1,8 @@
|
|
1
|
+
require 'rspec_api_docs/config'
|
1
2
|
require 'rspec_api_docs/version'
|
2
3
|
|
3
4
|
module RspecApiDocs
|
4
5
|
METADATA_NAMESPACE = :rspec_api_docs
|
5
6
|
|
6
|
-
|
7
|
-
attr_accessor :configuration
|
8
|
-
end
|
9
|
-
|
10
|
-
def self.configure
|
11
|
-
self.configuration ||= Config.new
|
12
|
-
yield configuration
|
13
|
-
end
|
14
|
-
|
15
|
-
class Config
|
16
|
-
attr_accessor :output_dir
|
17
|
-
|
18
|
-
def initialize
|
19
|
-
@output_dir = 'docs'
|
20
|
-
end
|
21
|
-
end
|
7
|
+
BaseError = Class.new(StandardError)
|
22
8
|
end
|
data/lib/rspec_api_docs/after.rb
CHANGED
@@ -1,10 +1,27 @@
|
|
1
|
+
require 'rspec_api_docs/after/type_checker'
|
2
|
+
|
1
3
|
module RspecApiDocs
|
2
|
-
|
3
|
-
|
4
|
+
UndocumentedParameter = Class.new(BaseError)
|
5
|
+
|
6
|
+
class After
|
7
|
+
Hook = -> (example) do
|
8
|
+
metadata = example.metadata[METADATA_NAMESPACE]
|
9
|
+
return unless metadata
|
4
10
|
|
5
|
-
if metadata
|
6
11
|
metadata[:requests] ||= []
|
7
12
|
metadata[:requests] << [last_request, last_response]
|
13
|
+
|
14
|
+
return unless RspecApiDocs.configuration.validate_params
|
15
|
+
|
16
|
+
metadata[:requests].each do |request, response|
|
17
|
+
request.params.each do |key, value|
|
18
|
+
if metadata[:parameters] && metadata[:parameters].has_key?(key.to_sym)
|
19
|
+
After::TypeChecker.call(type: metadata[:parameters][key.to_sym][:type], value: value)
|
20
|
+
else
|
21
|
+
raise UndocumentedParameter, "undocumented parameter included in request #{key.inspect}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
8
25
|
end
|
9
26
|
end
|
10
27
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module RspecApiDocs
|
2
|
+
class After
|
3
|
+
class TypeChecker
|
4
|
+
UnknownType = Class.new(BaseError)
|
5
|
+
TypeError = Class.new(BaseError)
|
6
|
+
|
7
|
+
attr_reader :type, :value
|
8
|
+
|
9
|
+
def self.call(*args)
|
10
|
+
new(*args).check
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(type:, value:)
|
14
|
+
@type = type
|
15
|
+
@value = value
|
16
|
+
end
|
17
|
+
|
18
|
+
def check
|
19
|
+
case type
|
20
|
+
when /integer/i
|
21
|
+
is_integer?(value) or raise_type_error
|
22
|
+
when /float/i
|
23
|
+
is_float?(value) or raise_type_error
|
24
|
+
when /boolean/i
|
25
|
+
is_bool?(value) or raise_type_error
|
26
|
+
when /string/i
|
27
|
+
# NO OP
|
28
|
+
else
|
29
|
+
raise UnknownType, "unknown type #{type.inspect}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def is_integer?(str)
|
36
|
+
Integer(str) && true
|
37
|
+
rescue ArgumentError
|
38
|
+
false
|
39
|
+
end
|
40
|
+
|
41
|
+
def is_float?(str)
|
42
|
+
Float(str) && true
|
43
|
+
rescue ArgumentError
|
44
|
+
false
|
45
|
+
end
|
46
|
+
|
47
|
+
def is_bool?(str)
|
48
|
+
%w[true false].include?(str)
|
49
|
+
end
|
50
|
+
|
51
|
+
def raise_type_error
|
52
|
+
raise TypeError, "wrong type #{value.inspect}, expected #{type.inspect}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|