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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 93a03551851fab62786002d514811191321bcab5
4
- data.tar.gz: cbde2ff103792b94141f1bb96092485b78882817
3
+ metadata.gz: 60a330ec02c82ad9a129e508386c67e173c0cd81
4
+ data.tar.gz: fed6d9f849bf9e1cee2cb44f04abce403353a064
5
5
  SHA512:
6
- metadata.gz: 2789ae22918606ca1d572f8025bd4930540dc097c8a2606c9b28867b12c8d17a2750e77926d381d0e44f5ddef3c7c6d0d652edd076eb4830ecef81b0924f8c0c
7
- data.tar.gz: af7acf8f314521c861a9c321be0939a93e6e8eab25e22b61a2a6cc3d4890d4f5f28ed41ac16f642e74b21eb6fd2036c6a6f2b667d116bc8c491fc80e8a74b1c6
6
+ metadata.gz: bc1785537527f94c561951c4ab62c6a0d37da817c26be4f55f8acbdaba7fc9cf14b31046c088a509b170a3fc36bb7484aa5c8d8fb93b956f5e7dd451572387cc
7
+ data.tar.gz: a258eba2025273e8f43a710d2f813c61633b7284d3a47714ed98b415557d54bbd4c6e88c0a29df19cd06597ad835a91cd16d5cc19cd78f4e7a20aab7cb2e416a
data/.gitignore CHANGED
@@ -1 +1,3 @@
1
1
  /Gemfile.lock
2
+ /pkg
3
+ /.yardoc
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
- # rspec-api-docs
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
- For now see the integration specs.
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
- rm -rf spec/integration/output
25
- rspec spec/integration/rspec_api_docs_spec.rb --format=RspecApiDocs::Formatter
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(:spec)
5
+ RSpec::Core::RakeTask.new :rspec do |task|
6
+ task.verbose = false
7
+ end
5
8
 
6
- task default: :spec
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
@@ -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
- class << self
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
@@ -1,10 +1,27 @@
1
+ require 'rspec_api_docs/after/type_checker'
2
+
1
3
  module RspecApiDocs
2
- After = -> (example) do
3
- metadata = example.metadata[METADATA_NAMESPACE]
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