rspec-apidoc 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 70f813f8f9f45e4f454cd07147cc1ff8dc22496ddd99e22bccf098b6f513f732
4
+ data.tar.gz: a817bac7f9c2f5bed38117de7634ae16520192f9f4a3864e685016d6f5e5d283
5
+ SHA512:
6
+ metadata.gz: c52748bb8d403685869114c402c36ee40ba0f1a3909871bc430cd753a4c74b626ad1d5f6252c5428d9ca628fe6757ae934ebbce923c9231e31205c77b989c722
7
+ data.tar.gz: b2f2f1f893a876532f404ecd47598e986f805b888503055668ed7144f2f972a45ef4783fb12df76602107adbdce9b6ef05338bb1321fb3195d6fa7cb231e655c
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Stas Suscov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # RSpec HTTP API Documentation 📠
2
+
3
+ Automatically generates the documentation for your HTTP API.
4
+
5
+ The idea is simple, you write the request specs, run your tests and you end
6
+ up with an HTML file with grouped examples of the request and response
7
+ examples.
8
+
9
+ ## But why?
10
+
11
+ I've tried a lot of tools in the hope to solve a simple problem: automatically
12
+ generate the API documentation based on a simple RSpec request test.
13
+
14
+ Tools like Blueprint, OpenAPI Swagger are great as a concept, but either
15
+ require some DSL (aka, more work on my side huh) or these are high maintenance
16
+ if your API design requires some flexibility and rapidly evolves.
17
+
18
+ Either way, there's only one single source of truth: **your tested code**.
19
+ This tool is just an easy way to help your front-end/mobile team how to
20
+ communicate with your HTTP API.
21
+
22
+ ## Installation
23
+
24
+ Add this line to your application's Gemfile:
25
+
26
+ ```ruby
27
+ gem 'rspec-apidoc'
28
+ ```
29
+
30
+ And then execute:
31
+
32
+ $ bundle install
33
+
34
+ Or install it yourself as:
35
+
36
+ $ gem install rspec-apidoc
37
+
38
+ ## Usage
39
+
40
+ Add this to your RSpec setup (eg. `spec/support/autoapi.rb`):
41
+
42
+ ```ruby
43
+ RSpec.configure do |config|
44
+ config.add_formatter(RSpec::Apidoc)
45
+ # Optionally add a visual formatter as well...
46
+ # config.add_formatter(:progress)
47
+
48
+ config.apidoc_title = 'YOUR.APP API Documentation'
49
+ config.apidoc_description = \
50
+ 'A longer intro to add before the examples: authtication, status codes...'
51
+
52
+ config.apidoc_host = 'https://api.YOUR.APP'
53
+ # Optionally specify the output file path.
54
+ config.apidoc_output_filename = 'apidoc.html'
55
+
56
+ # You can add it to any example based on the metadata.
57
+ config.after(:each, type: :request) do |example|
58
+ RSpec::Apidoc.add(self, example)
59
+ end
60
+ end
61
+ ```
62
+
63
+ ## Development
64
+
65
+ After checking out the repo, run `bundle` to install dependencies.
66
+
67
+ Then, run `rake spec` to run the tests.
68
+
69
+ To install this gem onto your local machine, run `bundle exec rake install`.
70
+
71
+ To release a new version, update the version number in `version.rb`, and then
72
+ run `bundle exec rake release`, which will create a git tag for the version,
73
+ push git commits and tags, and push the `.gem` file to
74
+ [rubygems.org](https://rubygems.org).
75
+
76
+ ## License
77
+
78
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
79
+
80
+ ## Code of Conduct
81
+
82
+ Everyone interacting with this project codebase, issue
83
+ tracker, chat rooms and mailing list is expected to follow the [code of
84
+ conduct](https://github.com/[USERNAME]/active_record-pgcrypto/blob/master/CODE_OF_CONDUCT.md).
@@ -0,0 +1,140 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title><%= title %></title>
6
+ <style>
7
+ html { font-family: sans-serif; }
8
+ .header {
9
+ border-bottom: 1px solid #EEE;
10
+ padding: 30px;
11
+ text-transform: uppercase;
12
+ font-size: 250%;
13
+ font-weight: bold;
14
+ }
15
+ .footer { padding: 60px; border-top: 1px solid #EEE; color: #CCC; }
16
+ #examples { display: flex; }
17
+ #toc {
18
+ border-right: 1px solid #EEE;
19
+ text-transform: uppercase;
20
+ max-width: 30%;
21
+ }
22
+ #toc ul { list-style-type:none; margin: 0; padding: 0; }
23
+ #toc li a, #toc li span {
24
+ display: block;
25
+ padding: 30px;
26
+ border-bottom: 1px solid #EEE;
27
+ }
28
+ #toc li span { font-weight: bold; color: #555; }
29
+ #docs { width: 100%; max-width: 70%; }
30
+ pre {
31
+ background: #444;
32
+ color: #CCC;
33
+ padding: 20px;
34
+ margin-top: 0;
35
+ white-space: pre-wrap;
36
+ overflow: auto;
37
+ }
38
+ .example-doc {
39
+ border-bottom: 1px solid #DDD;
40
+ padding-bottom: 30px;
41
+ margin-bottom: 30px;
42
+ }
43
+ .example { padding: 30px 60px; }
44
+ .example:nth-child(even) { background: #EEE; }
45
+ .request-response { display: flex; justify-content: space-between; }
46
+ .request, .response { width: 45%; }
47
+ .request span, .response span {
48
+ padding: 10px;
49
+ background: #CCC;
50
+ color: #333;
51
+ font-weight: bold;
52
+ border-radius: 10px 10px 0 0;
53
+ text-transform: uppercase;
54
+ font-size: 70%;
55
+ display: block;
56
+ }
57
+ .arrow-up { font-weight: bold; font-size: 150%; }
58
+ </style>
59
+ </head>
60
+ <body>
61
+ <div class="header">
62
+ <span><%= title %></span>
63
+ </div>
64
+
65
+ <div id="examples">
66
+ <div id="toc">
67
+ <ul>
68
+ <li><a href="#examples">Home</a></li>
69
+ <% examples.each do |controller, items| %>
70
+ <li>
71
+ <span><%= controller %></span>
72
+ <ul>
73
+ <% items.keys.sort.each do |action| %>
74
+ <li>
75
+ <a href="#<%= controller %>-<%= action %>">
76
+ <%= action %>
77
+ </a>
78
+ </li>
79
+ <% end %>
80
+ </ul>
81
+ </li>
82
+ <% end %>
83
+ </ul>
84
+ </div>
85
+
86
+ <div id="docs">
87
+ <div class="example">
88
+ <h1><%= title %></h1>
89
+ <%= description %>
90
+ </div>
91
+
92
+ <% examples.each do |controller, items| %>
93
+ <% items.sort.each do |action, action_items| %>
94
+ <% action_items.each_with_index do |item, index| %>
95
+ <div class="example">
96
+ <% if index == 0 %>
97
+ <div class="example-doc" id="<%= controller %>-<%= action %>">
98
+ <%= item[:action_comment] || ( item[:method] + ' ' + controller ) %>
99
+ </div>
100
+ <% end %>
101
+
102
+ <p><%= item[:description] %></>
103
+ <div class="request-response">
104
+ <div class="request">
105
+ <span>Request</span>
106
+ <pre>
107
+ $ curl '<%= host %><%= item[:path] %><%= ('?' + item[:query]) if item[:query] != '' %>' \
108
+ -X <%= item[:method] %> \
109
+ -H 'Content-Type: <%= item[:content_type] %>' <% if item[:request_body] != '' %> \
110
+ @- &lt;&lt; EOF
111
+ <%=h JSON.pretty_generate(item[:request_body]) %>
112
+ EOF
113
+ <% end %>
114
+ </pre>
115
+ </div>
116
+ <div class="response">
117
+ <span>Response</span>
118
+ <pre>
119
+ &lt; Content-Type: <%= item[:response_content_type] %>
120
+ &lt; Status: <%= item[:status] %>
121
+ <%=h JSON.pretty_generate(item[:response_body]) %>
122
+ </pre>
123
+ </div>
124
+ </div>
125
+
126
+ <p>
127
+ <a class=".arrow-up" href="#examples">&#11205;</a>
128
+ </p>
129
+ </div>
130
+
131
+ <% end %>
132
+ <% end %>
133
+ <% end %>
134
+ </div>
135
+ </div>
136
+ <div class="footer">
137
+ &copy; <%= Date.current.year %> <%= title %>
138
+ </div>
139
+ </body>
140
+ </html>
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/core/formatters/base_formatter'
4
+ require 'method_source'
5
+ require 'erb'
6
+
7
+ # rubocop:disable Style/Documentation
8
+ module RSpec
9
+ # rubocop:enable Style/Documentation
10
+
11
+ # Add our settings...
12
+ configure do |config|
13
+ config.add_setting(:apidoc_title)
14
+ config.add_setting(:apidoc_description)
15
+ config.add_setting(:apidoc_host)
16
+ config.add_setting(
17
+ :apidoc_template_path,
18
+ default: File.expand_path('apidoc/static/template.html.erb', __dir__)
19
+ )
20
+ config.add_setting(:apidoc_output_filename, default: 'apidoc.html')
21
+ end
22
+
23
+ # Our formatter class.
24
+ #
25
+ # We're using the formatter as the hooks do not allow to report when the
26
+ # test runner is done.
27
+ class Apidoc < RSpec::Core::Formatters::BaseFormatter
28
+ include ERB::Util
29
+
30
+ # We want only passed tests registered and know when the runner is finished
31
+ RSpec::Core::Formatters.register self, :example_passed, :close
32
+
33
+ # Returns the title for our docs
34
+ #
35
+ # @return [String]
36
+ def title
37
+ RSpec.configuration.apidoc_title
38
+ end
39
+
40
+ # Returns the description for our docs
41
+ #
42
+ # @return [String]
43
+ def description
44
+ RSpec.configuration.apidoc_description
45
+ end
46
+
47
+ # Returns the template path used to render our docs
48
+ #
49
+ # @return [String]
50
+ def template_path
51
+ RSpec.configuration.apidoc_template_path
52
+ end
53
+
54
+ # Returns the final document path for our docs
55
+ #
56
+ # @return [String]
57
+ def output_filename
58
+ RSpec.configuration.apidoc_output_filename
59
+ end
60
+
61
+ # Returns the API host used to generate the `curl` commands
62
+ #
63
+ # @return [String]
64
+ def host
65
+ RSpec.configuration.apidoc_host
66
+ end
67
+
68
+ # Returns the collected examples, sorted
69
+ #
70
+ # @return [Array] a list of [Hash] items
71
+ def examples
72
+ @examples ||= {}
73
+ @examples = @examples.sort.to_h
74
+ end
75
+
76
+ # Parses and stores relevant to our docs data in the example metadata
77
+ #
78
+ # @return [Hash] the metadata that was added
79
+ # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
80
+ def self.add(spec, example)
81
+ request_body = parse_json_safely(spec.request.body.string)
82
+ response_body = parse_json_safely(spec.response.body)
83
+
84
+ if spec.request.controller_instance
85
+ action_comment = spec.request.controller_class.instance_method(
86
+ spec.request.controller_instance.action_name
87
+ ).comment
88
+ # Remove any `@param` or `@return` lines
89
+ action_comment = action_comment.delete('#').gsub(/@.*$/, '').strip
90
+ controller_class = spec.request.controller_class
91
+ action_name = spec.request.controller_instance.action_name
92
+ else
93
+ action_comment = nil
94
+ controller_class = spec.request.path
95
+ action_name = spec.request.method
96
+ end
97
+
98
+ example.metadata[:apidoc_data] = {
99
+ description: example.metadata[:full_description],
100
+
101
+ controller_class: controller_class,
102
+ action_name: action_name,
103
+ action_comment: action_comment,
104
+
105
+ content_type: spec.request.content_type,
106
+ method: spec.request.method,
107
+ path: spec.request.path,
108
+ query: spec.request.query_parameters.to_param,
109
+ request_body: request_body,
110
+
111
+ status: spec.response.status,
112
+ response_content_type: spec.response.content_type,
113
+ response_body: response_body
114
+ }
115
+ end
116
+ # rubocop:enable Metrics/AbcSize,Metrics/MethodLength
117
+
118
+ # Returns a parsed JSON object
119
+ #
120
+ # @param obj [Object] an object to parse as JSON
121
+ # @return [Object]
122
+ def self.parse_json_safely(obj)
123
+ JSON.parse(obj)
124
+ rescue StandardError
125
+ obj.to_s
126
+ end
127
+
128
+ # Formatter callback, stores the example metadata for later to be used
129
+ #
130
+ # @return [Hash] the metadata that was stored
131
+ def example_passed(notification)
132
+ apidoc_data = notification.example.metadata[:apidoc_data]
133
+
134
+ return if apidoc_data.nil?
135
+
136
+ controller_name = apidoc_data[:controller_class].to_s
137
+ controller_examples = examples[controller_name] ||= {}
138
+ controller_examples[apidoc_data[:action_name]] ||= []
139
+ controller_examples[apidoc_data[:action_name]].append(apidoc_data)
140
+ end
141
+
142
+ # Formatter callback, generates the HTML from the metadata we stored
143
+ #
144
+ # @return [Integer] size of the new file.
145
+ def close(_notification)
146
+ return if examples.empty?
147
+
148
+ erb = ERB.new(File.read(template_path))
149
+ File.write(output_filename, erb.result(self.binding))
150
+ end
151
+ end
152
+ end
metadata ADDED
@@ -0,0 +1,161 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rspec-apidoc
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Stas Suscov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-11-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: method_source
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec-core
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec-rails
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop-performance
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop-rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: simplecov
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: Automatically generates the documentation for your HTTP API.
126
+ email:
127
+ - stas@nerd.ro
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - LICENSE.txt
133
+ - README.md
134
+ - lib/rspec/apidoc.rb
135
+ - lib/rspec/apidoc/static/template.html.erb
136
+ homepage: https://github.com/HeyBetter/rspec-apidoc
137
+ licenses:
138
+ - MIT
139
+ metadata:
140
+ homepage_uri: https://github.com/HeyBetter/rspec-apidoc
141
+ source_code_uri: https://github.com/HeyBetter/rspec-apidoc
142
+ post_install_message:
143
+ rdoc_options: []
144
+ require_paths:
145
+ - lib
146
+ required_ruby_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: 2.7.0
151
+ required_rubygems_version: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - ">="
154
+ - !ruby/object:Gem::Version
155
+ version: '0'
156
+ requirements: []
157
+ rubygems_version: 3.2.22
158
+ signing_key:
159
+ specification_version: 4
160
+ summary: RSpec HTTP API Documentation
161
+ test_files: []