rspec-api-docs 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 366d34d11131e28d3d3c88024ae9ffce5d2d2f27
4
- data.tar.gz: 6a143737560c1d8ac8630a35761da4ac45e1efab
3
+ metadata.gz: eee6ccdbceb3b61271298301b471e3d09a9b7edb
4
+ data.tar.gz: 8a1e3b753359755cde4ca89586ffd5f3163d3d4e
5
5
  SHA512:
6
- metadata.gz: c144b0cb4b2e0684c141fa92fe5edba7492e2dcd8e28adfd7f02ce530326848efb0cb3179753d8e36e529b03af53742ebf2fec77cf36345d00ba3bb328ff8f82
7
- data.tar.gz: 7eb7c7a1b0283bfacecde7db7069c116809e0adf6a1a48969549281716e370d4fd24bcff1898717124fdf03d319ce0b904c6fa7a0aecf24ab99a887fa500d109
6
+ metadata.gz: 97555c1200f96e8370b333fc9ff86e5ffb011a782fa51aab4231d74527d1e0d21484bad7326233b7248c3cedb816fc29e1fdc91d851704810ea2a07216e2c98c
7
+ data.tar.gz: 6f93f8c19a225543f023a737e52a0ac8c6829c7fd62d97ed8f62b8c3e21d5697599e7594f73f41e51e3d5b8be6e15bad7ed1f57ef6be23899577b81fedb44cc5
data/README.md CHANGED
@@ -154,6 +154,12 @@ Accepts a string that describes the resource.
154
154
 
155
155
  > Characters inhabit the Land of Ooo.
156
156
 
157
+ #### `resource_precedence`
158
+
159
+ Accepts an optional integer.
160
+
161
+ Lower numbers are ordered first.
162
+
157
163
  #### `name`
158
164
 
159
165
  Accepts a string of the name of the resource.
@@ -185,20 +191,24 @@ Note: This defaults to the path of the first route requested in the example.
185
191
 
186
192
  #### `field`
187
193
 
188
- Accepts a `name`, `description`, and optionally a `scope` and `type`.
194
+ Accepts a `name`, `description`, and optionally a `scope`, `type`, and `example`.
189
195
 
190
196
  - `name` [`Symbol`] the name of the response field
191
197
  - `description` [`String`] a description of the response field
192
198
  - `scope` [`Symbol`, `Array<Symbol>`] _(optional)_ how the field is scoped
193
199
  - `type` [`String`] _(optional)_ the type of the returned field
200
+ - `example` _(optional)_ an example value
194
201
 
195
202
  This can be called multiple times for each response field.
196
203
 
197
204
  ``` ruby
198
- field :id, 'The id of a character', scope: :character, type: 'integer'
205
+ field :id, 'The id of a character', scope: :character, type: 'integer', example: 42
199
206
  field :name, "The character's name", scope: :character, type: 'string'
200
207
  ```
201
208
 
209
+ The `example` is useful if the data might change (i.e. a database ID column).
210
+ The value will be substituted in the resulting JSON.
211
+
202
212
  #### `param`
203
213
 
204
214
  Accepts a `name`, `description`, and optionally a `scope`, `type`, and `required` flag.
@@ -228,6 +238,12 @@ note 'You need to supply an id!'
228
238
  note :warning, "An error will be thrown if you don't supply an id!"
229
239
  ```
230
240
 
241
+ #### `precedence`
242
+
243
+ Accepts an optional integer.
244
+
245
+ Lower numbers are ordered first.
246
+
231
247
  See the integration specs for more examples of the DSL in use.
232
248
 
233
249
  ### Formatter
@@ -255,6 +271,34 @@ end
255
271
 
256
272
  See [the documentation](http://www.rubydoc.info/github/twe4ked/rspec-api-docs/master).
257
273
 
274
+ ## Rake tasks
275
+
276
+ ``` ruby
277
+ require 'rspec_api_docs/rake_task'
278
+
279
+ RspecApiDocs::Rake.new do |task| # docs:generate
280
+ # Pattern for where to find the specs
281
+ task.pattern = 'spec/requests/**/*_spec.rb'
282
+
283
+ # Extra RSpec options
284
+ task.rspec_opts = ['--format progress']
285
+ end
286
+
287
+ RspecApiDocs::Rake.new do |task| # docs:ensure_updated
288
+ # Same as options above with some extras for when verify is true
289
+
290
+ # Raise an error if the generated docs don't match the existing docs
291
+ # The verify option only works with the :json renderer.
292
+ task.verify = true
293
+
294
+ # The existing (committed) output file
295
+ task.existing_file = 'docs/index.json'
296
+ end
297
+
298
+ # Non-verify task with custom name
299
+ RspecApiDocs::Rake.new :custom_task_name
300
+ ```
301
+
258
302
  ## Development
259
303
 
260
304
  After checking out the repo, run `bin/setup` to install dependencies. Then, run
@@ -271,11 +315,6 @@ Regenerate this project's integration spec docs locally:
271
315
  $ ./bin/generate_integration_docs
272
316
  ```
273
317
 
274
- ## TODO
275
-
276
- - Allow specifying an order (`precedence`?)
277
- - Order Resources and Examples alphabetically
278
-
279
318
  ## Contributing
280
319
 
281
320
  Bug reports and pull requests are welcome on GitHub at
data/Rakefile CHANGED
@@ -24,6 +24,15 @@ RspecApiDocs::RakeTask.new do |task|
24
24
  ]
25
25
  task.pattern = 'spec/integration/rspec_api_docs_spec.rb'
26
26
  task.existing_file = 'spec/integration/output/json/index.json'
27
+ task.verify = true
28
+ end
29
+
30
+ RspecApiDocs::RakeTask.new do |task|
31
+ task.verbose = false
32
+ task.rspec_opts = [
33
+ '--format progress',
34
+ ]
35
+ task.pattern = 'spec/integration/rspec_api_docs_spec.rb'
27
36
  end
28
37
 
29
38
  task default: [:rspec, :rubocop, :generate_integration_docs]
@@ -1,3 +1,5 @@
1
+ # TODO: Move Resource out of formatter dir
2
+ require 'rspec_api_docs/formatter/resource'
1
3
  require 'rspec_api_docs/after/type_checker'
2
4
 
3
5
  module RspecApiDocs
@@ -15,8 +17,11 @@ module RspecApiDocs
15
17
 
16
18
  metadata[:requests].each do |request, response|
17
19
  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
+ parameter = RspecApiDocs::Resource::Example.new(example).parameters
21
+ .select { |parameter| parameter.name == key.to_sym }.first
22
+
23
+ if parameter
24
+ After::TypeChecker.call(type: parameter.type, value: value)
20
25
  else
21
26
  raise UndocumentedParameter, "undocumented parameter included in request #{key.inspect}"
22
27
  end
@@ -36,6 +36,15 @@ module RspecApiDocs
36
36
  metadata[METADATA_NAMESPACE][:resource_description] = value
37
37
  end
38
38
 
39
+ # For setting the precedence of the resource
40
+ #
41
+ # Lower numbers will be ordered higher
42
+ #
43
+ # @param value [Integer] the precedence
44
+ def resource_precedence(value)
45
+ metadata[METADATA_NAMESPACE][:resource_precedence] = value
46
+ end
47
+
39
48
  # For setting a description of the example.
40
49
  #
41
50
  # E.g. "Allows you to return a single character."
@@ -76,13 +85,15 @@ module RspecApiDocs
76
85
  # @param description [String] a description of the response field
77
86
  # @param scope [Symbol, Array<Symbol>] how the field is scoped
78
87
  # @param type [String]
88
+ # @param example an example value
79
89
  # @return [void]
80
- def field(name, description, scope: [], type: nil)
90
+ def field(name, description, scope: [], type: nil, example: nil)
81
91
  metadata[METADATA_NAMESPACE][:fields] ||= {}
82
- metadata[METADATA_NAMESPACE][:fields][name] = {
92
+ metadata[METADATA_NAMESPACE][:fields][{name: name, scope: scope}] = {
83
93
  description: description,
84
94
  scope: Array(scope),
85
95
  type: type,
96
+ example: example,
86
97
  }
87
98
  end
88
99
 
@@ -107,7 +118,7 @@ module RspecApiDocs
107
118
  # @return [void]
108
119
  def param(name, description, scope: [], type: nil, required: false)
109
120
  metadata[METADATA_NAMESPACE][:parameters] ||= {}
110
- metadata[METADATA_NAMESPACE][:parameters][name] = {
121
+ metadata[METADATA_NAMESPACE][:parameters][{name: name, scope: scope}] = {
111
122
  description: description,
112
123
  scope: Array(scope),
113
124
  type: type,
@@ -126,6 +137,15 @@ module RspecApiDocs
126
137
  metadata[METADATA_NAMESPACE][:note] ||= {}
127
138
  metadata[METADATA_NAMESPACE][:note][level] = value
128
139
  end
140
+
141
+ # For setting the precedence of an example
142
+ #
143
+ # Lower numbers will be ordered higher
144
+ #
145
+ # @param value [Integer] the precedence
146
+ def precedence(value)
147
+ metadata[METADATA_NAMESPACE][:example_precedence] = value
148
+ end
129
149
  end
130
150
  end
131
151
  end
@@ -40,7 +40,7 @@ module RspecApiDocs
40
40
  #
41
41
  # @return [void]
42
42
  def close(null_notification)
43
- renderer.new(resources.values.sort_by(&:name)).render
43
+ renderer.new(resources.values.sort_by { |resource| [resource.precedence, resource.name].join }).render
44
44
  end
45
45
 
46
46
  private
@@ -33,7 +33,7 @@ module RspecApiDocs
33
33
  #
34
34
  # @return [Array<Example>]
35
35
  def examples
36
- @examples.sort_by(&:name)
36
+ @examples.sort_by { |example| [example.precedence, example.name].join }
37
37
  end
38
38
 
39
39
  # Add an example
@@ -43,6 +43,15 @@ module RspecApiDocs
43
43
  @examples << example
44
44
  end
45
45
 
46
+ # @return [String, nil]
47
+ def precedence
48
+ metadata[:resource_precedence]
49
+ end
50
+
51
+ def inspect
52
+ "#<RspecApiDocs::Resource #{name.inspect}, @examples=#{examples.inspect}>"
53
+ end
54
+
46
55
  private
47
56
 
48
57
  def metadata
@@ -1,4 +1,5 @@
1
1
  require 'rspec_api_docs/formatter/resource/example/request_headers'
2
+ require 'rspec_api_docs/formatter/resource/example/deep_hash_set'
2
3
 
3
4
  module RspecApiDocs
4
5
  class Resource
@@ -31,8 +32,8 @@ module RspecApiDocs
31
32
  #
32
33
  # @return [Array<Parameter>]
33
34
  def parameters
34
- metadata.fetch(:parameters, []).map do |name, parameter|
35
- Parameter.new(name, parameter)
35
+ metadata.fetch(:parameters, []).map do |name_hash, parameter|
36
+ Parameter.new(name_hash[:name], parameter)
36
37
  end
37
38
  end
38
39
 
@@ -40,8 +41,8 @@ module RspecApiDocs
40
41
  #
41
42
  # @return [Array<ResponseField>]
42
43
  def response_fields
43
- metadata.fetch(:fields, []).map do |name, field|
44
- ResponseField.new(name, field)
44
+ metadata.fetch(:fields, []).map do |name_hash, field|
45
+ ResponseField.new(name_hash[:name], field)
45
46
  end
46
47
  end
47
48
 
@@ -89,6 +90,15 @@ module RspecApiDocs
89
90
  metadata.fetch(:note, {})
90
91
  end
91
92
 
93
+ # @return [String, nil]
94
+ def precedence
95
+ metadata[:example_precedence]
96
+ end
97
+
98
+ def inspect
99
+ "#<RspecApiDocs::Resource::Example #{name.inspect}>"
100
+ end
101
+
92
102
  private
93
103
 
94
104
  def request_response_pairs
@@ -111,7 +121,15 @@ module RspecApiDocs
111
121
  end
112
122
 
113
123
  def response_body(body)
114
- body.empty? ? nil : body
124
+ unless body.empty?
125
+ parsed_body = JSON.parse(body, symbolize_names: true)
126
+ response_fields.each do |f|
127
+ unless f.example.nil?
128
+ DeepHashSet.call(parsed_body, f.scope + [f.name], f.example)
129
+ end
130
+ end
131
+ JSON.dump(parsed_body)
132
+ end
115
133
  end
116
134
 
117
135
  def response_status_text(status)
@@ -0,0 +1,58 @@
1
+ module RspecApiDocs
2
+ class Resource
3
+ class Example
4
+ class DeepHashSet
5
+ attr_reader :hash, :keys, :value, :node
6
+
7
+ def self.call(*args)
8
+ new(*args).call
9
+ end
10
+
11
+ def initialize(hash, keys, value)
12
+ @hash = hash
13
+ @keys = keys
14
+ @value = value
15
+ @node = []
16
+ end
17
+
18
+ def call
19
+ keys.each_with_index do |key, index|
20
+ case
21
+ when key.empty? # TODO: should this require `key == []`?
22
+ deep_set_value_at_array(index)
23
+ break
24
+ when index == keys.size - 1
25
+ set_value_at(key)
26
+ else
27
+ node << key
28
+ end
29
+ end
30
+
31
+ hash
32
+ end
33
+
34
+ private
35
+
36
+ attr_reader :node
37
+
38
+ def deep_set_value_at_array(index)
39
+ array = deep_find(hash, node)
40
+ array && array.each do |inner_hash|
41
+ DeepHashSet.call(inner_hash, keys[index+1..-1], value)
42
+ end
43
+ end
44
+
45
+ def set_value_at(key)
46
+ part = deep_find(hash, node)
47
+ if part.is_a?(Hash) && !part[key].nil?
48
+ part[key] = value
49
+ end
50
+ end
51
+
52
+ def deep_find(hash, keys)
53
+ keys.inject(hash) { |h, k| h && h[k] }
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -34,6 +34,11 @@ module RspecApiDocs
34
34
  name == other.name &&
35
35
  parameter == other.parameter
36
36
  end
37
+
38
+ # @return [String, nil]
39
+ def type
40
+ parameter[:type]
41
+ end
37
42
  end
38
43
  end
39
44
  end
@@ -29,6 +29,11 @@ module RspecApiDocs
29
29
  field[:description]
30
30
  end
31
31
 
32
+ # Example value
33
+ def example
34
+ field[:example]
35
+ end
36
+
32
37
  # @return [true, false]
33
38
  def ==(other)
34
39
  name == other.name &&
@@ -10,23 +10,23 @@ module RspecApiDocs
10
10
  extend RSpec::Matchers
11
11
  end
12
12
 
13
- attr_reader :name
14
-
15
13
  attr_accessor \
16
14
  :verbose,
17
15
  :pattern,
18
16
  :rspec_opts,
19
17
  :existing_file,
20
- :dir
18
+ :dir,
19
+ :verify
21
20
 
22
21
  def initialize(name = nil, &block)
23
- @name = name || :'docs:ensure_updated'
22
+ @name = name
24
23
  @verbose = true
25
24
  @pattern = 'spec/requests/**/*_spec.rb'
26
25
  @rspec_opts = []
27
26
  @existing_file = 'docs/index.json'
27
+ @verify = false
28
28
 
29
- block.call(self)
29
+ block.call(self) if block
30
30
 
31
31
  define
32
32
  end
@@ -34,17 +34,13 @@ module RspecApiDocs
34
34
  private
35
35
 
36
36
  def define
37
- desc 'Ensure API docs are up to date'
37
+ desc default_desc
38
38
  task name do
39
- @dir = Dir.mktmpdir
39
+ @dir = Dir.mktmpdir if verify
40
40
 
41
41
  rspec_task.run_task(verbose)
42
42
 
43
- configure_rspec
44
-
45
- RSpecMatchers.expect(generated).to RSpecMatchers.eq(existing)
46
-
47
- remove_dir
43
+ verify! if verify
48
44
  end
49
45
  end
50
46
 
@@ -59,11 +55,7 @@ module RspecApiDocs
59
55
  def rspec_task
60
56
  RSpec::Core::RakeTask.new.tap do |task|
61
57
  task.pattern = pattern
62
- task.rspec_opts = rspec_opts + [
63
- '--format RspecApiDocs::Formatter',
64
- '--order defined',
65
- "--require #{spec_helper.path}",
66
- ]
58
+ task.rspec_opts = task_rspec_opts
67
59
  end
68
60
  end
69
61
 
@@ -87,5 +79,31 @@ module RspecApiDocs
87
79
  def remove_dir
88
80
  FileUtils.remove_entry dir
89
81
  end
82
+
83
+ def verify!
84
+ configure_rspec
85
+
86
+ RSpecMatchers.expect(generated).to RSpecMatchers.eq(existing)
87
+
88
+ remove_dir
89
+ end
90
+
91
+ def task_rspec_opts
92
+ arr = rspec_opts + [
93
+ '--format RspecApiDocs::Formatter',
94
+ '--order defined',
95
+ ]
96
+ arr += ["--require #{spec_helper.path}"] if verify
97
+ arr
98
+ end
99
+
100
+ def name
101
+ @name ||
102
+ verify ? :'docs:ensure_updated' : :'docs:generate'
103
+ end
104
+
105
+ def default_desc
106
+ verify ? 'Ensure API docs are up to date' : 'Generate API docs'
107
+ end
90
108
  end
91
109
  end
@@ -1,3 +1,3 @@
1
1
  module RspecApiDocs
2
- VERSION = '0.8.0'
2
+ VERSION = '0.9.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspec-api-docs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Odin Dutton
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-01-05 00:00:00.000000000 Z
11
+ date: 2017-01-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -149,6 +149,7 @@ files:
149
149
  - lib/rspec_api_docs/formatter/renderer/slate_renderer/slate_index.html.md.erb
150
150
  - lib/rspec_api_docs/formatter/resource.rb
151
151
  - lib/rspec_api_docs/formatter/resource/example.rb
152
+ - lib/rspec_api_docs/formatter/resource/example/deep_hash_set.rb
152
153
  - lib/rspec_api_docs/formatter/resource/example/request_headers.rb
153
154
  - lib/rspec_api_docs/formatter/resource/parameter.rb
154
155
  - lib/rspec_api_docs/formatter/resource/response_field.rb