rspec_api_documentation 4.9.0 → 6.1.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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/lib/rspec_api_documentation/api_documentation.rb +2 -0
  3. data/lib/rspec_api_documentation/client_base.rb +8 -8
  4. data/lib/rspec_api_documentation/configuration.rb +12 -1
  5. data/lib/rspec_api_documentation/curl.rb +7 -2
  6. data/lib/rspec_api_documentation/dsl/endpoint/params.rb +19 -3
  7. data/lib/rspec_api_documentation/dsl/endpoint/set_param.rb +18 -5
  8. data/lib/rspec_api_documentation/dsl/endpoint.rb +44 -2
  9. data/lib/rspec_api_documentation/dsl/resource.rb +48 -1
  10. data/lib/rspec_api_documentation/dsl.rb +1 -1
  11. data/lib/rspec_api_documentation/example.rb +4 -0
  12. data/lib/rspec_api_documentation/open_api/contact.rb +9 -0
  13. data/lib/rspec_api_documentation/open_api/example.rb +7 -0
  14. data/lib/rspec_api_documentation/open_api/header.rb +12 -0
  15. data/lib/rspec_api_documentation/open_api/headers.rb +7 -0
  16. data/lib/rspec_api_documentation/open_api/helper.rb +29 -0
  17. data/lib/rspec_api_documentation/open_api/info.rb +12 -0
  18. data/lib/rspec_api_documentation/open_api/license.rb +8 -0
  19. data/lib/rspec_api_documentation/open_api/node.rb +112 -0
  20. data/lib/rspec_api_documentation/open_api/operation.rb +18 -0
  21. data/lib/rspec_api_documentation/open_api/parameter.rb +33 -0
  22. data/lib/rspec_api_documentation/open_api/path.rb +13 -0
  23. data/lib/rspec_api_documentation/open_api/paths.rb +7 -0
  24. data/lib/rspec_api_documentation/open_api/response.rb +10 -0
  25. data/lib/rspec_api_documentation/open_api/responses.rb +9 -0
  26. data/lib/rspec_api_documentation/open_api/root.rb +21 -0
  27. data/lib/rspec_api_documentation/open_api/schema.rb +15 -0
  28. data/lib/rspec_api_documentation/open_api/security_definitions.rb +7 -0
  29. data/lib/rspec_api_documentation/open_api/security_schema.rb +14 -0
  30. data/lib/rspec_api_documentation/open_api/tag.rb +9 -0
  31. data/lib/rspec_api_documentation/railtie.rb +1 -1
  32. data/lib/rspec_api_documentation/views/api_blueprint_example.rb +116 -0
  33. data/lib/rspec_api_documentation/views/api_blueprint_index.rb +105 -0
  34. data/lib/rspec_api_documentation/views/markdown_example.rb +1 -1
  35. data/lib/rspec_api_documentation/views/markup_example.rb +12 -3
  36. data/lib/rspec_api_documentation/views/markup_index.rb +2 -4
  37. data/lib/rspec_api_documentation/views/slate_index.rb +4 -0
  38. data/lib/rspec_api_documentation/writers/api_blueprint_writer.rb +29 -0
  39. data/lib/rspec_api_documentation/writers/combined_json_writer.rb +1 -1
  40. data/lib/rspec_api_documentation/writers/general_markup_writer.rb +20 -7
  41. data/lib/rspec_api_documentation/writers/json_iodocs_writer.rb +3 -2
  42. data/lib/rspec_api_documentation/writers/json_writer.rb +10 -6
  43. data/lib/rspec_api_documentation/writers/markdown_writer.rb +1 -1
  44. data/lib/rspec_api_documentation/writers/open_api_writer.rb +244 -0
  45. data/lib/rspec_api_documentation/writers/slate_writer.rb +2 -8
  46. data/lib/rspec_api_documentation.rb +29 -0
  47. data/templates/rspec_api_documentation/api_blueprint_index.mustache +80 -0
  48. data/templates/rspec_api_documentation/html_index.mustache +1 -1
  49. data/templates/rspec_api_documentation/markdown_example.mustache +4 -3
  50. data/templates/rspec_api_documentation/markdown_index.mustache +1 -0
  51. data/templates/rspec_api_documentation/slate_example.mustache +2 -2
  52. data/templates/rspec_api_documentation/slate_index.mustache +8 -0
  53. data/templates/rspec_api_documentation/textile_index.mustache +1 -0
  54. metadata +61 -3
@@ -0,0 +1,9 @@
1
+ module RspecApiDocumentation
2
+ module OpenApi
3
+ class Responses < Node
4
+ CHILD_CLASS = Response
5
+
6
+ add_setting :default, :default => lambda { |responses| responses.existing_settings.size > 1 ? nil : Response.new }
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,21 @@
1
+ module RspecApiDocumentation
2
+ module OpenApi
3
+ class Root < Node
4
+ add_setting :swagger, :default => '2.0', :required => true
5
+ add_setting :info, :default => Info.new, :required => true, :schema => Info
6
+ add_setting :host, :default => 'localhost:3000'
7
+ add_setting :basePath
8
+ add_setting :schemes, :default => %w(http https)
9
+ add_setting :consumes, :default => %w(application/json application/xml)
10
+ add_setting :produces, :default => %w(application/json application/xml)
11
+ add_setting :paths, :default => Paths.new, :required => true, :schema => Paths
12
+ add_setting :definitions
13
+ add_setting :parameters
14
+ add_setting :responses
15
+ add_setting :securityDefinitions, :schema => SecurityDefinitions
16
+ add_setting :security
17
+ add_setting :tags, :default => [], :schema => [Tag]
18
+ add_setting :externalDocs
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,15 @@
1
+ module RspecApiDocumentation
2
+ module OpenApi
3
+ class Schema < Node
4
+ add_setting :format
5
+ add_setting :title
6
+ add_setting :description, :default => ''
7
+ add_setting :required
8
+ add_setting :enum
9
+ add_setting :type
10
+ add_setting :items
11
+ add_setting :properties
12
+ add_setting :example
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ module RspecApiDocumentation
2
+ module OpenApi
3
+ class SecurityDefinitions < Node
4
+ CHILD_CLASS = SecuritySchema
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,14 @@
1
+ module RspecApiDocumentation
2
+ module OpenApi
3
+ class SecuritySchema < Node
4
+ add_setting :type, :required => true
5
+ add_setting :description, :default => ''
6
+ add_setting :name
7
+ add_setting :in
8
+ add_setting :flow
9
+ add_setting :authorizationUrl
10
+ add_setting :tokenUrl
11
+ add_setting :scopes
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,9 @@
1
+ module RspecApiDocumentation
2
+ module OpenApi
3
+ class Tag < Node
4
+ add_setting :name, :required => true
5
+ add_setting :description, :default => ''
6
+ add_setting :externalDocs
7
+ end
8
+ end
9
+ 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,116 @@
1
+ module RspecApiDocumentation
2
+ module Views
3
+ class ApiBlueprintExample < MarkupExample
4
+ TOTAL_SPACES_INDENTATION = 12.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(remove_content_type(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(remove_content_type(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
+ # `Content-Type` header is removed because the information would be duplicated
50
+ # since it's already present in `request[:request_content_type]`.
51
+ def remove_content_type(headers)
52
+ return unless headers
53
+ headers
54
+ .split("\n")
55
+ .reject { |header|
56
+ header.start_with?('Content-Type:')
57
+ }
58
+ .join("\n")
59
+ end
60
+
61
+ def has_request?(metadata)
62
+ metadata.any? do |key, value|
63
+ [:request_body, :request_headers, :request_content_type].include?(key) && value
64
+ end
65
+ end
66
+
67
+ def has_response?(metadata)
68
+ metadata.any? do |key, value|
69
+ [:response_status, :response_body, :response_headers, :response_content_type].include?(key) && value
70
+ end
71
+ end
72
+
73
+ def indent(string)
74
+ string.tap do |str|
75
+ str.gsub!(/\n/, "\n" + (" " * TOTAL_SPACES_INDENTATION)) if str
76
+ end
77
+ end
78
+
79
+ # http_call: the hash that contains all information about the HTTP
80
+ # request and response.
81
+ # message_direction: either `request` or `response`.
82
+ def body_to_json(http_call, message_direction)
83
+ content_type = http_call["#{message_direction}_content_type".to_sym]
84
+ body = http_call["#{message_direction}_body".to_sym] # e.g request_body
85
+
86
+ if json?(content_type) && body
87
+ body = JSON.pretty_generate(JSON.parse(body))
88
+ end
89
+
90
+ body
91
+ end
92
+
93
+ # JSON requests should use UTF-8 by default according to
94
+ # http://www.ietf.org/rfc/rfc4627.txt, so we will remove `charset=utf-8`
95
+ # when we find it to remove noise.
96
+ def remove_utf8_for_json(headers)
97
+ return unless headers
98
+ headers
99
+ .split("\n")
100
+ .map { |header|
101
+ header.gsub!(/; *charset=utf-8/, "") if json?(header)
102
+ header
103
+ }
104
+ .join("\n")
105
+ end
106
+
107
+ def content_type(headers)
108
+ headers && headers.fetch("Content-Type", nil)
109
+ end
110
+
111
+ def json?(string)
112
+ string =~ /application\/.*json/
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,105 @@
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}#{e.route_name}" }.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: format_route(examples[0]),
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 follows the RFC 6570 to format URI templates.
49
+ # According to it, simple string expansion (used to perform variable
50
+ # expansion) should be represented by `{var}` and not by `/:var`
51
+ # For example `/posts/:id` should become `/posts/{id}`
52
+ # cf. https://github.com/apiaryio/api-blueprint/blob/format-1A/API%20Blueprint%20Specification.md#431-resource-section
53
+ # cf. https://tools.ietf.org/html/rfc6570#section-3.2.6
54
+ def format_route(example)
55
+ route_uri = example[:route_uri].gsub(/:(.*?)([.\/?{]|$)/, '{\1}\2')
56
+ "#{route_uri}#{example[:route_optionals]}"
57
+ end
58
+
59
+ # APIB has both `parameters` and `attributes`. This generates a hash
60
+ # with all of its properties, like name, description, required.
61
+ # {
62
+ # required: true,
63
+ # example: "1",
64
+ # type: "string",
65
+ # name: "id",
66
+ # description: "The id",
67
+ # properties_description: "required, string"
68
+ # }
69
+ def fields(property_name, examples)
70
+ examples
71
+ .map { |example| example.metadata[property_name] }
72
+ .flatten
73
+ .compact
74
+ .uniq { |property| property[:name] }
75
+ .map do |property|
76
+ properties = []
77
+ if property[:required] == true
78
+ properties << 'required'
79
+ else
80
+ properties << 'optional'
81
+ end
82
+ properties << property[:type] if property[:type]
83
+ if properties.count > 0
84
+ property[:properties_description] = properties.join(", ")
85
+ else
86
+ property[:properties_description] = nil
87
+ end
88
+
89
+ property[:description] = nil if description_blank?(property)
90
+ property
91
+ end
92
+ end
93
+
94
+ # When no `description` was specified for a parameter, the DSL class
95
+ # is making `description = "#{scope} #{name}"`, which is bad because it
96
+ # assumes that all formats want this behavior. To avoid changing there
97
+ # and breaking everything, I do my own check here and if description
98
+ # equals the name, I assume it is blank.
99
+ def description_blank?(property)
100
+ !property[:description] ||
101
+ property[:description].to_s.strip == property[:name].to_s.strip
102
+ end
103
+ end
104
+ end
105
+ end
@@ -1,7 +1,7 @@
1
1
  module RspecApiDocumentation
2
2
  module Views
3
3
  class MarkdownExample < MarkupExample
4
- EXTENSION = 'markdown'
4
+ EXTENSION = 'md'
5
5
 
6
6
  def initialize(example, configuration)
7
7
  super
@@ -3,6 +3,8 @@ require 'mustache'
3
3
  module RspecApiDocumentation
4
4
  module Views
5
5
  class MarkupExample < Mustache
6
+ SPECIAL_CHARS = /[<>:"\/\\|?*]/.freeze
7
+
6
8
  def initialize(example, configuration)
7
9
  @example = example
8
10
  @host = configuration.curl_host
@@ -19,12 +21,11 @@ module RspecApiDocumentation
19
21
  end
20
22
 
21
23
  def dirname
22
- resource_name.to_s.downcase.gsub(/\s+/, '_').gsub(":", "_")
24
+ sanitize(resource_name.to_s.downcase)
23
25
  end
24
26
 
25
27
  def filename
26
- special_chars = /[<>:"\/\\|?*]/
27
- basename = description.downcase.gsub(/\s+/, '_').gsub(special_chars, '')
28
+ basename = sanitize(description.downcase)
28
29
  basename = Digest::MD5.new.update(description).to_s if basename.blank?
29
30
  "#{basename}.#{extension}"
30
31
  end
@@ -83,6 +84,14 @@ module RspecApiDocumentation
83
84
  end
84
85
  end.join
85
86
  end
87
+
88
+ def content_type(headers)
89
+ headers && headers.fetch("Content-Type", nil)
90
+ end
91
+
92
+ def sanitize(name)
93
+ name.gsub(/\s+/, '_').gsub(SPECIAL_CHARS, '')
94
+ end
86
95
  end
87
96
  end
88
97
  end
@@ -3,16 +3,14 @@ require 'mustache'
3
3
  module RspecApiDocumentation
4
4
  module Views
5
5
  class MarkupIndex < Mustache
6
+ delegate :api_name, :api_explanation, to: :@configuration, prefix: false
7
+
6
8
  def initialize(index, configuration)
7
9
  @index = index
8
10
  @configuration = configuration
9
11
  self.template_path = configuration.template_path
10
12
  end
11
13
 
12
- def api_name
13
- @configuration.api_name
14
- end
15
-
16
14
  def sections
17
15
  RspecApiDocumentation::Writers::IndexHelper.sections(examples, @configuration)
18
16
  end
@@ -1,6 +1,10 @@
1
1
  module RspecApiDocumentation
2
2
  module Views
3
3
  class SlateIndex < MarkdownIndex
4
+ def initialize(index, configuration)
5
+ super
6
+ self.template_name = "rspec_api_documentation/slate_index"
7
+ end
4
8
  end
5
9
  end
6
10
  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
@@ -7,7 +7,7 @@ module RspecApiDocumentation
7
7
  File.open(configuration.docs_dir.join("combined.json"), "w+") do |f|
8
8
  examples = []
9
9
  index.examples.each do |rspec_example|
10
- examples << Formatter.to_json(JsonExample.new(rspec_example, configuration))
10
+ examples << Formatter.to_json(JSONExample.new(rspec_example, configuration))
11
11
  end
12
12
 
13
13
  f.write "["
@@ -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
@@ -32,7 +32,7 @@ module RspecApiDocumentation
32
32
  end
33
33
 
34
34
  def examples
35
- @index.examples.map { |example| JsonExample.new(example, @configuration) }
35
+ @index.examples.map { |example| JsonIodocsExample.new(example, @configuration) }
36
36
  end
37
37
 
38
38
  def as_json(opts = nil)
@@ -48,7 +48,7 @@ module RspecApiDocumentation
48
48
  end
49
49
  end
50
50
 
51
- class JsonExample
51
+ class JsonIodocsExample
52
52
  def initialize(example, configuration)
53
53
  @example = example
54
54
  end
@@ -94,6 +94,7 @@ module RspecApiDocumentation
94
94
  {
95
95
  @api_key.to_sym => {
96
96
  :name => @configuration.api_name,
97
+ :description => @configuration.api_explanation,
97
98
  :protocol => @configuration.io_docs_protocol,
98
99
  :publicPath => "",
99
100
  :baseURL => @configuration.curl_host
@@ -2,19 +2,19 @@ require 'rspec_api_documentation/writers/formatter'
2
2
 
3
3
  module RspecApiDocumentation
4
4
  module Writers
5
- class JsonWriter < Writer
5
+ class JSONWriter < Writer
6
6
  delegate :docs_dir, :to => :configuration
7
7
 
8
8
  def write
9
9
  File.open(docs_dir.join("index.json"), "w+") do |f|
10
- f.write Formatter.to_json(JsonIndex.new(index, configuration))
10
+ f.write Formatter.to_json(JSONIndex.new(index, configuration))
11
11
  end
12
12
  write_examples
13
13
  end
14
14
 
15
15
  def write_examples
16
16
  index.examples.each do |example|
17
- json_example = JsonExample.new(example, configuration)
17
+ json_example = JSONExample.new(example, configuration)
18
18
  FileUtils.mkdir_p(docs_dir.join(json_example.dirname))
19
19
  File.open(docs_dir.join(json_example.dirname, json_example.filename), "w+") do |f|
20
20
  f.write Formatter.to_json(json_example)
@@ -23,7 +23,11 @@ module RspecApiDocumentation
23
23
  end
24
24
  end
25
25
 
26
- class JsonIndex
26
+ # https://github.com/zipmark/rspec_api_documentation/issues/382
27
+ # backward compatibilty json for configuration of config.format
28
+ class JsonWriter < JSONWriter; end
29
+
30
+ class JSONIndex
27
31
  def initialize(index, configuration)
28
32
  @index = index
29
33
  @configuration = configuration
@@ -34,7 +38,7 @@ module RspecApiDocumentation
34
38
  end
35
39
 
36
40
  def examples
37
- @index.examples.map { |example| JsonExample.new(example, @configuration) }
41
+ @index.examples.map { |example| JSONExample.new(example, @configuration) }
38
42
  end
39
43
 
40
44
  def as_json(opts = nil)
@@ -61,7 +65,7 @@ module RspecApiDocumentation
61
65
  end
62
66
  end
63
67
 
64
- class JsonExample
68
+ class JSONExample
65
69
  def initialize(example, configuration)
66
70
  @example = example
67
71
  @host = configuration.curl_host
@@ -1,7 +1,7 @@
1
1
  module RspecApiDocumentation
2
2
  module Writers
3
3
  class MarkdownWriter < GeneralMarkupWriter
4
- EXTENSION = 'markdown'
4
+ EXTENSION = 'md'
5
5
 
6
6
  def markup_index_class
7
7
  RspecApiDocumentation::Views::MarkdownIndex