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