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