rspec-api-docs 0.8.0 → 0.9.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 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