rspec_api_documentation 4.9.0 → 5.0.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: 16b47e6bedc1c86cc5e418233eca8b80d16b5628
4
- data.tar.gz: e1da890616a2fa4cf6a8ff8c44eff5977abc32f6
3
+ metadata.gz: eab76d5c83c6d908b0d547852bf776001916a096
4
+ data.tar.gz: e104765306842544aae1e13e425df1048898490e
5
5
  SHA512:
6
- metadata.gz: c16d4338f5d6e06f1c913a951c1370b31f2e6c85a6aef38a593d5fbd332226ad99c9fae76207e9bed5089500010a5b588a123e0354cdef25d057cbc0483a1933
7
- data.tar.gz: 03ef3fbf5306d2951fa6f3b82f08e2679306069e791ef9bf5d30f58693dbb83528bb78009821cc4b26fb1da166c33620c1e5d327c9d91f8008bd45afa93eead4
6
+ metadata.gz: 950451c2559d9a185e3afd901ce7253c8c11d47dc2dff603e210b7d38fcb591cf0f15382113919f4c6bda2b4d90488a7c9c9fd7170ce39047c2091436694259c
7
+ data.tar.gz: 7f319b73f94430152e6a6d161fb63f61abfb7d2bffc73109298a7caea084325ee9deba64425880b1991a0c9d04ac025a8542448e252ff7a867947f0b68def133
@@ -44,6 +44,7 @@ module RspecApiDocumentation
44
44
  autoload :CombinedTextWriter
45
45
  autoload :CombinedJsonWriter
46
46
  autoload :SlateWriter
47
+ autoload :ApiBlueprintWriter
47
48
  end
48
49
 
49
50
  module Views
@@ -59,6 +60,8 @@ module RspecApiDocumentation
59
60
  autoload :MarkdownExample
60
61
  autoload :SlateIndex
61
62
  autoload :SlateExample
63
+ autoload :ApiBlueprintIndex
64
+ autoload :ApiBlueprintExample
62
65
  end
63
66
 
64
67
  def self.configuration
@@ -96,14 +96,14 @@ module RspecApiDocumentation
96
96
  end
97
97
 
98
98
  def clean_out_uploaded_data(params, request_body)
99
- params.each do |_, value|
100
- if value.is_a?(Hash)
101
- if value.has_key?(:tempfile)
102
- data = value[:tempfile].read
103
- request_body = request_body.gsub(data, "[uploaded data]")
104
- else
105
- request_body = clean_out_uploaded_data(value,request_body)
106
- end
99
+ params.each do |value|
100
+ if [Hash, Array].member? value.class
101
+ request_body = if value.respond_to?(:has_key?) && value.has_key?(:tempfile)
102
+ data = value[:tempfile].read
103
+ request_body.gsub(data, "[uploaded data]")
104
+ else
105
+ clean_out_uploaded_data(value, request_body)
106
+ end
107
107
  end
108
108
  end
109
109
  request_body
@@ -16,7 +16,7 @@ module RspecApiDocumentation
16
16
  end
17
17
 
18
18
  def get
19
- "curl \"#{url}#{get_data}\" -X GET #{headers}"
19
+ "curl -g \"#{url}#{get_data}\" -X GET #{headers}"
20
20
  end
21
21
 
22
22
  def head
@@ -9,6 +9,8 @@ module RspecApiDocumentation::DSL
9
9
  extend ActiveSupport::Concern
10
10
  include Rack::Test::Utils
11
11
 
12
+ URL_PARAMS_REGEX = /[:\{](\w+)\}?/.freeze
13
+
12
14
  delegate :response_headers, :response_status, :response_body, :to => :rspec_api_documentation_client
13
15
 
14
16
  module ClassMethods
@@ -96,8 +98,16 @@ module RspecApiDocumentation::DSL
96
98
  rspec_api_documentation_client.status
97
99
  end
98
100
 
101
+ def in_path?(param)
102
+ path_params.include?(param)
103
+ end
104
+
105
+ def path_params
106
+ example.metadata[:route].scan(URL_PARAMS_REGEX).flatten
107
+ end
108
+
99
109
  def path
100
- example.metadata[:route].gsub(/:(\w+)/) do |match|
110
+ example.metadata[:route].gsub(URL_PARAMS_REGEX) do |match|
101
111
  if extra_params.keys.include?($1)
102
112
  delete_extra_param($1)
103
113
  elsif respond_to?($1)
@@ -8,7 +8,12 @@ module RspecApiDocumentation::DSL
8
8
  define_method method do |*args, &block|
9
9
  options = args.extract_options!
10
10
  options[:method] = method
11
- options[:route] = args.first
11
+ if metadata[:route_uri]
12
+ options[:route] = metadata[:route_uri]
13
+ options[:action_name] = args.first
14
+ else
15
+ options[:route] = args.first
16
+ end
12
17
  options[:api_doc_dsl] = :endpoint
13
18
  args.push(options)
14
19
  args[0] = "#{method.to_s.upcase} #{args[0]}"
@@ -38,10 +43,25 @@ module RspecApiDocumentation::DSL
38
43
  context(*args, &block)
39
44
  end
40
45
 
46
+ def route(*args, &block)
47
+ raise "You must define the route URI" if args[0].blank?
48
+ raise "You must define the route name" if args[1].blank?
49
+ options = args.extract_options!
50
+ options[:route_uri] = args[0].gsub(/\{.*\}/, "")
51
+ options[:route_optionals] = (optionals = args[0].match(/(\{.*\})/) and optionals[-1])
52
+ options[:route_name] = args[1]
53
+ args.push(options)
54
+ context(*args, &block)
55
+ end
56
+
41
57
  def parameter(name, *args)
42
58
  parameters.push(field_specification(name, *args))
43
59
  end
44
60
 
61
+ def attribute(name, *args)
62
+ attributes.push(field_specification(name, *args))
63
+ end
64
+
45
65
  def response_field(name, *args)
46
66
  response_fields.push(field_specification(name, *args))
47
67
  end
@@ -75,6 +95,10 @@ module RspecApiDocumentation::DSL
75
95
  safe_metadata(:parameters, [])
76
96
  end
77
97
 
98
+ def attributes
99
+ safe_metadata(:attributes, [])
100
+ end
101
+
78
102
  def response_fields
79
103
  safe_metadata(:response_fields, [])
80
104
  end
@@ -38,6 +38,10 @@ module RspecApiDocumentation
38
38
  respond_to?(:parameters) && parameters.present?
39
39
  end
40
40
 
41
+ def has_attributes?
42
+ respond_to?(:attributes) && attributes.present?
43
+ end
44
+
41
45
  def has_response_fields?
42
46
  respond_to?(:response_fields) && response_fields.present?
43
47
  end
@@ -1,7 +1,7 @@
1
1
  module RspecApiDocumentation
2
2
  class Railtie < Rails::Railtie
3
3
  rake_tasks do
4
- load "tasks/docs.rake"
4
+ load File.join(File.dirname(__FILE__), '../tasks/docs.rake')
5
5
  end
6
6
  end
7
7
  end
@@ -0,0 +1,104 @@
1
+ module RspecApiDocumentation
2
+ module Views
3
+ class ApiBlueprintExample < MarkupExample
4
+ TOTAL_SPACES_INDENTATION = 8.freeze
5
+
6
+ def initialize(example, configuration)
7
+ super
8
+ self.template_name = "rspec_api_documentation/api_blueprint_example"
9
+ end
10
+
11
+ def parameters
12
+ super.map do |parameter|
13
+ parameter.merge({
14
+ :required => !!parameter[:required],
15
+ :has_example => !!parameter[:example],
16
+ :has_type => !!parameter[:type]
17
+ })
18
+ end
19
+ end
20
+
21
+ def requests
22
+ super.map do |request|
23
+ request[:request_headers_text] = remove_utf8_for_json(request[:request_headers_text])
24
+ request[:request_headers_text] = indent(request[:request_headers_text])
25
+ request[:request_content_type] = content_type(request[:request_headers])
26
+ request[:request_content_type] = remove_utf8_for_json(request[:request_content_type])
27
+ request[:request_body] = body_to_json(request, :request)
28
+ request[:request_body] = indent(request[:request_body])
29
+
30
+ request[:response_headers_text] = remove_utf8_for_json(request[:response_headers_text])
31
+ request[:response_headers_text] = indent(request[:response_headers_text])
32
+ request[:response_content_type] = content_type(request[:response_headers])
33
+ request[:response_content_type] = remove_utf8_for_json(request[:response_content_type])
34
+ request[:response_body] = body_to_json(request, :response)
35
+ request[:response_body] = indent(request[:response_body])
36
+
37
+ request[:has_request?] = has_request?(request)
38
+ request[:has_response?] = has_response?(request)
39
+ request
40
+ end
41
+ end
42
+
43
+ def extension
44
+ Writers::ApiBlueprintWriter::EXTENSION
45
+ end
46
+
47
+ private
48
+
49
+ def has_request?(metadata)
50
+ metadata.any? do |key, value|
51
+ [:request_body, :request_headers, :request_content_type].include?(key) && value
52
+ end
53
+ end
54
+
55
+ def has_response?(metadata)
56
+ metadata.any? do |key, value|
57
+ [:response_status, :response_body, :response_headers, :response_content_type].include?(key) && value
58
+ end
59
+ end
60
+
61
+ def indent(string)
62
+ string.tap do |str|
63
+ str.gsub!(/\n/, "\n" + (" " * TOTAL_SPACES_INDENTATION)) if str
64
+ end
65
+ end
66
+
67
+ # http_call: the hash that contains all information about the HTTP
68
+ # request and response.
69
+ # message_direction: either `request` or `response`.
70
+ def body_to_json(http_call, message_direction)
71
+ content_type = http_call["#{message_direction}_content_type".to_sym]
72
+ body = http_call["#{message_direction}_body".to_sym] # e.g request_body
73
+
74
+ if json?(content_type) && body
75
+ body = JSON.pretty_generate(JSON.parse(body))
76
+ end
77
+
78
+ body
79
+ end
80
+
81
+ # JSON requests should use UTF-8 by default according to
82
+ # http://www.ietf.org/rfc/rfc4627.txt, so we will remove `charset=utf-8`
83
+ # when we find it to remove noise.
84
+ def remove_utf8_for_json(headers)
85
+ return unless headers
86
+ headers
87
+ .split("\n")
88
+ .map { |header|
89
+ header.gsub!(/; *charset=utf-8/, "") if json?(header)
90
+ header
91
+ }
92
+ .join("\n")
93
+ end
94
+
95
+ def content_type(headers)
96
+ headers && headers.fetch("Content-Type", nil)
97
+ end
98
+
99
+ def json?(string)
100
+ string =~ /application\/.*json/
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,90 @@
1
+ module RspecApiDocumentation
2
+ module Views
3
+ class ApiBlueprintIndex < MarkupIndex
4
+ def initialize(index, configuration)
5
+ super
6
+ self.template_name = "rspec_api_documentation/api_blueprint_index"
7
+ end
8
+
9
+ def sections
10
+ super.map do |section|
11
+ routes = section[:examples].group_by { |e| "#{e.route_uri}#{e.route_optionals}" }.map do |route, examples|
12
+ attrs = fields(:attributes, examples)
13
+ params = fields(:parameters, examples)
14
+
15
+ methods = examples.group_by(&:http_method).map do |http_method, examples|
16
+ {
17
+ http_method: http_method,
18
+ description: examples.first.respond_to?(:action_name) && examples.first.action_name,
19
+ examples: examples
20
+ }
21
+ end
22
+
23
+ {
24
+ "has_attributes?".to_sym => attrs.size > 0,
25
+ "has_parameters?".to_sym => params.size > 0,
26
+ route: route,
27
+ route_name: examples[0][:route_name],
28
+ attributes: attrs,
29
+ parameters: params,
30
+ http_methods: methods
31
+ }
32
+ end
33
+
34
+ section.merge({
35
+ routes: routes
36
+ })
37
+ end
38
+ end
39
+
40
+ def examples
41
+ @index.examples.map do |example|
42
+ ApiBlueprintExample.new(example, @configuration)
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ # APIB has both `parameters` and `attributes`. This generates a hash
49
+ # with all of its properties, like name, description, required.
50
+ # {
51
+ # required: true,
52
+ # example: "1",
53
+ # type: "string",
54
+ # name: "id",
55
+ # description: "The id",
56
+ # properties_description: "required, string"
57
+ # }
58
+ def fields(property_name, examples)
59
+ examples
60
+ .map { |example| example.metadata[property_name] }
61
+ .flatten
62
+ .compact
63
+ .uniq { |property| property[:name] }
64
+ .map do |property|
65
+ properties = []
66
+ properties << "required" if property[:required]
67
+ properties << property[:type] if property[:type]
68
+ if properties.count > 0
69
+ property[:properties_description] = properties.join(", ")
70
+ else
71
+ property[:properties_description] = nil
72
+ end
73
+
74
+ property[:description] = nil if description_blank?(property)
75
+ property
76
+ end
77
+ end
78
+
79
+ # When no `description` was specified for a parameter, the DSL class
80
+ # is making `description = "#{scope} #{name}"`, which is bad because it
81
+ # assumes that all formats want this behavior. To avoid changing there
82
+ # and breaking everything, I do my own check here and if description
83
+ # equals the name, I assume it is blank.
84
+ def description_blank?(property)
85
+ !property[:description] ||
86
+ property[:description].to_s.strip == property[:name].to_s.strip
87
+ end
88
+ end
89
+ end
90
+ end
@@ -83,6 +83,10 @@ module RspecApiDocumentation
83
83
  end
84
84
  end.join
85
85
  end
86
+
87
+ def content_type(headers)
88
+ headers && headers.fetch("Content-Type", nil)
89
+ end
86
90
  end
87
91
  end
88
92
  end
@@ -0,0 +1,29 @@
1
+ module RspecApiDocumentation
2
+ module Writers
3
+ class ApiBlueprintWriter < GeneralMarkupWriter
4
+ EXTENSION = 'apib'
5
+
6
+ def markup_index_class
7
+ RspecApiDocumentation::Views::ApiBlueprintIndex
8
+ end
9
+
10
+ def markup_example_class
11
+ RspecApiDocumentation::Views::ApiBlueprintExample
12
+ end
13
+
14
+ def extension
15
+ EXTENSION
16
+ end
17
+
18
+ private
19
+
20
+ # API Blueprint is a spec, not navigable like HTML, therefore we generate
21
+ # only one file with all resources.
22
+ def render_options
23
+ super.merge({
24
+ examples: false
25
+ })
26
+ end
27
+ end
28
+ end
29
+ end
@@ -6,16 +6,20 @@ module RspecApiDocumentation
6
6
 
7
7
  # Write out the generated documentation
8
8
  def write
9
- File.open(configuration.docs_dir.join(index_file_name + '.' + extension), "w+") do |f|
10
- f.write markup_index_class.new(index, configuration).render
9
+ if render_options.fetch(:index, true)
10
+ File.open(configuration.docs_dir.join(index_file_name + '.' + extension), "w+") do |f|
11
+ f.write markup_index_class.new(index, configuration).render
12
+ end
11
13
  end
12
14
 
13
- index.examples.each do |example|
14
- markup_example = markup_example_class.new(example, configuration)
15
- FileUtils.mkdir_p(configuration.docs_dir.join(markup_example.dirname))
15
+ if render_options.fetch(:examples, true)
16
+ index.examples.each do |example|
17
+ markup_example = markup_example_class.new(example, configuration)
18
+ FileUtils.mkdir_p(configuration.docs_dir.join(markup_example.dirname))
16
19
 
17
- File.open(configuration.docs_dir.join(markup_example.dirname, markup_example.filename), "w+") do |f|
18
- f.write markup_example.render
20
+ File.open(configuration.docs_dir.join(markup_example.dirname, markup_example.filename), "w+") do |f|
21
+ f.write markup_example.render
22
+ end
19
23
  end
20
24
  end
21
25
  end
@@ -27,6 +31,15 @@ module RspecApiDocumentation
27
31
  def extension
28
32
  raise 'Parent class. This method should not be called.'
29
33
  end
34
+
35
+ private
36
+
37
+ def render_options
38
+ {
39
+ index: true,
40
+ examples: true
41
+ }
42
+ end
30
43
  end
31
44
  end
32
45
  end
@@ -0,0 +1,79 @@
1
+ FORMAT: A1
2
+ {{# sections }}
3
+
4
+ # Group {{ resource_name }}
5
+ {{# resource_explanation }}
6
+
7
+ {{{ resource_explanation }}}
8
+ {{/ resource_explanation }}
9
+ {{# description }}
10
+
11
+ {{ description }}
12
+ {{/ description }}
13
+ {{# routes }}
14
+
15
+ ## {{ route_name }} [{{ route }}]
16
+ {{# description }}
17
+
18
+ description: {{ description }}
19
+ {{/ description }}
20
+ {{# explanation }}
21
+
22
+ explanation: {{ explanation }}
23
+ {{/ explanation }}
24
+ {{# has_parameters? }}
25
+
26
+ + Parameters
27
+ {{# parameters }}
28
+ + {{ name }}{{# example }}: {{ example }}{{/ example }}{{# properties_description }} ({{ properties_description }}){{/ properties_description }}{{# description }} - {{ description }}{{/ description }}
29
+ {{/ parameters }}
30
+ {{/ has_parameters? }}
31
+ {{# has_attributes? }}
32
+
33
+ + Attributes (object)
34
+ {{# attributes }}
35
+ + {{ name }}{{# example }}: {{ example }}{{/ example }}{{# properties_description }} ({{ properties_description }}){{/ properties_description }}{{# description }} - {{ description }}{{/ description }}
36
+ {{/ attributes }}
37
+ {{/ has_attributes? }}
38
+ {{# http_methods }}
39
+
40
+ ### {{ description }} [{{ http_method }}]
41
+ {{# examples }}
42
+ {{# requests }}
43
+ {{# has_request? }}
44
+
45
+ + Request {{ description }}{{# request_content_type }} ({{ request_content_type }}){{/ request_content_type }}
46
+ {{/ has_request? }}
47
+ {{# request_headers_text }}
48
+
49
+ + Headers
50
+
51
+ {{{ request_headers_text }}}
52
+ {{/ request_headers_text }}
53
+ {{# request_body }}
54
+
55
+ + Body
56
+
57
+ {{{ request_body }}}
58
+ {{/ request_body }}
59
+ {{# has_response? }}
60
+
61
+ + Response {{ response_status }} ({{ response_content_type }})
62
+ {{/ has_response? }}
63
+ {{# response_headers_text }}
64
+
65
+ + Headers
66
+
67
+ {{{ response_headers_text }}}
68
+ {{/ response_headers_text }}
69
+ {{# response_body }}
70
+
71
+ + Body
72
+
73
+ {{{ response_body }}}
74
+ {{/ response_body }}
75
+ {{/ requests }}
76
+ {{/ examples }}
77
+ {{/ http_methods }}
78
+ {{/ routes }}
79
+ {{/ sections }}
@@ -25,10 +25,11 @@
25
25
  {{# has_response_fields? }}
26
26
 
27
27
  ### Response Fields
28
- {{# response_fields }}
29
28
 
30
- Name : {{ name }}
31
- Description : {{ description }}
29
+ | Name | Description | Scope |
30
+ |------|-------------|-------|
31
+ {{# response_fields }}
32
+ | {{ name }} | {{ description }} | {{ scope }} |
32
33
  {{/ response_fields }}
33
34
 
34
35
  {{/ has_response_fields? }}
@@ -9,7 +9,7 @@
9
9
  #### Endpoint
10
10
 
11
11
  {{# requests}}
12
- ```
12
+ ```plaintext
13
13
  {{ request_method }} {{ request_path }}
14
14
  {{ request_headers_text }}
15
15
  ```
@@ -50,7 +50,7 @@ None known.
50
50
 
51
51
  ### Response
52
52
 
53
- ```
53
+ ```plaintext
54
54
  {{ response_headers_text }}
55
55
  {{ response_status }} {{ response_status_text}}
56
56
  ```
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspec_api_documentation
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.9.0
4
+ version: 5.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Cahoon
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2016-12-08 00:00:00.000000000 Z
13
+ date: 2017-06-06 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rspec
@@ -282,6 +282,8 @@ files:
282
282
  - lib/rspec_api_documentation/rack_test_client.rb
283
283
  - lib/rspec_api_documentation/railtie.rb
284
284
  - lib/rspec_api_documentation/test_server.rb
285
+ - lib/rspec_api_documentation/views/api_blueprint_example.rb
286
+ - lib/rspec_api_documentation/views/api_blueprint_index.rb
285
287
  - lib/rspec_api_documentation/views/html_example.rb
286
288
  - lib/rspec_api_documentation/views/html_index.rb
287
289
  - lib/rspec_api_documentation/views/markdown_example.rb
@@ -292,6 +294,7 @@ files:
292
294
  - lib/rspec_api_documentation/views/slate_index.rb
293
295
  - lib/rspec_api_documentation/views/textile_example.rb
294
296
  - lib/rspec_api_documentation/views/textile_index.rb
297
+ - lib/rspec_api_documentation/writers/api_blueprint_writer.rb
295
298
  - lib/rspec_api_documentation/writers/append_json_writer.rb
296
299
  - lib/rspec_api_documentation/writers/combined_json_writer.rb
297
300
  - lib/rspec_api_documentation/writers/combined_text_writer.rb
@@ -306,6 +309,7 @@ files:
306
309
  - lib/rspec_api_documentation/writers/textile_writer.rb
307
310
  - lib/rspec_api_documentation/writers/writer.rb
308
311
  - lib/tasks/docs.rake
312
+ - templates/rspec_api_documentation/api_blueprint_index.mustache
309
313
  - templates/rspec_api_documentation/html_example.mustache
310
314
  - templates/rspec_api_documentation/html_index.mustache
311
315
  - templates/rspec_api_documentation/markdown_example.mustache