lazy_api_doc 0.1.6 → 0.2.2
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 +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +22 -3
- data/docs/example/api.yml +1355 -0
- data/docs/example/index.html +24 -0
- data/docs/example/layout.yml +14 -0
- data/lib/generators/lazy_api_doc/install_generator.rb +42 -14
- data/lib/lazy_api_doc/generator.rb +22 -15
- data/lib/lazy_api_doc/route_parser.rb +6 -6
- data/lib/lazy_api_doc/variants_parser.rb +2 -2
- data/lib/lazy_api_doc/version.rb +1 -1
- data/lib/lazy_api_doc.rb +92 -51
- data/screenshot.png +0 -0
- metadata +6 -2
@@ -0,0 +1,24 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>My Application Documentation</title>
|
5
|
+
<!-- needed for adaptive design -->
|
6
|
+
<meta charset="utf-8"/>
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
8
|
+
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
|
9
|
+
|
10
|
+
<!--
|
11
|
+
ReDoc doesn't change outer page styles
|
12
|
+
-->
|
13
|
+
<style>
|
14
|
+
body {
|
15
|
+
margin: 0;
|
16
|
+
padding: 0;
|
17
|
+
}
|
18
|
+
</style>
|
19
|
+
</head>
|
20
|
+
<body>
|
21
|
+
<redoc spec-url='./api.yml'></redoc>
|
22
|
+
<script src="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"></script>
|
23
|
+
</body>
|
24
|
+
</html>
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'rails/generators'
|
2
|
+
require 'lazy_api_doc'
|
2
3
|
|
3
4
|
module LazyApiDoc
|
4
5
|
module Generators
|
@@ -7,8 +8,17 @@ module LazyApiDoc
|
|
7
8
|
|
8
9
|
desc "Copy base configuration for LazyApiDoc"
|
9
10
|
def install
|
10
|
-
copy_file 'public/index.html',
|
11
|
-
copy_file 'public/layout.yml',
|
11
|
+
copy_file 'public/index.html', "#{LazyApiDoc.path}/index.html"
|
12
|
+
copy_file 'public/layout.yml', "#{LazyApiDoc.path}/layout.yml"
|
13
|
+
|
14
|
+
append_to_file '.gitignore' do
|
15
|
+
<<~TXT
|
16
|
+
|
17
|
+
# LazyApiDoc
|
18
|
+
#{LazyApiDoc.path}/api.yml
|
19
|
+
#{LazyApiDoc.path}/examples/*.json
|
20
|
+
TXT
|
21
|
+
end
|
12
22
|
|
13
23
|
install_rspec if Dir.exist?('spec')
|
14
24
|
|
@@ -21,18 +31,27 @@ module LazyApiDoc
|
|
21
31
|
copy_file 'support/rspec_interceptor.rb', 'spec/support/lazy_api_doc_interceptor.rb'
|
22
32
|
|
23
33
|
insert_into_file 'spec/rails_helper.rb', after: "RSpec.configure do |config|\n" do
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
34
|
+
<<-RUBY
|
35
|
+
if ENV['LAZY_API_DOC']
|
36
|
+
require 'lazy_api_doc'
|
37
|
+
require 'support/lazy_api_doc_interceptor'
|
28
38
|
|
29
|
-
|
30
|
-
|
39
|
+
config.include LazyApiDocInterceptor, type: :request
|
40
|
+
config.include LazyApiDocInterceptor, type: :controller
|
31
41
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
42
|
+
config.after(:suite) do
|
43
|
+
# begin: Handle ParallelTests
|
44
|
+
# This peace of code handle using ParallelTests (tests runs in independent processes).
|
45
|
+
# Just delete this block if you don't use ParallelTests
|
46
|
+
if ENV['TEST_ENV_NUMBER'] && defined?(ParallelTests)
|
47
|
+
LazyApiDoc.save_examples('rspec')
|
48
|
+
ParallelTests.wait_for_other_processes_to_finish if ParallelTests.first_process?
|
49
|
+
LazyApiDoc.load_examples
|
50
|
+
end
|
51
|
+
# end: Handle ParallelTests
|
52
|
+
LazyApiDoc.generate_documentation
|
53
|
+
end
|
54
|
+
end
|
36
55
|
RUBY
|
37
56
|
end
|
38
57
|
end
|
@@ -43,7 +62,7 @@ module LazyApiDoc
|
|
43
62
|
append_to_file 'test/test_helper.rb' do
|
44
63
|
<<~RUBY
|
45
64
|
|
46
|
-
if ENV['
|
65
|
+
if ENV['LAZY_API_DOC']
|
47
66
|
require 'lazy_api_doc'
|
48
67
|
require 'support/lazy_api_doc_interceptor'
|
49
68
|
|
@@ -52,7 +71,16 @@ module LazyApiDoc
|
|
52
71
|
end
|
53
72
|
|
54
73
|
Minitest.after_run do
|
55
|
-
|
74
|
+
# begin: Handle ParallelTests
|
75
|
+
# This peace of code handle using ParallelTests (tests runs in independent processes).
|
76
|
+
# Just delete this block if you don't use ParallelTests
|
77
|
+
if ENV['TEST_ENV_NUMBER'] && defined?(ParallelTests)
|
78
|
+
LazyApiDoc.save_examples('minitest')
|
79
|
+
ParallelTests.wait_for_other_processes_to_finish if ParallelTests.first_process?
|
80
|
+
LazyApiDoc.load_examples
|
81
|
+
end
|
82
|
+
# end: Handle ParallelTests
|
83
|
+
LazyApiDoc.generate_documentation
|
56
84
|
end
|
57
85
|
end
|
58
86
|
RUBY
|
@@ -9,24 +9,33 @@ module LazyApiDoc
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def add(example)
|
12
|
-
return if example[
|
12
|
+
return if example['controller'] == "anonymous" # don't handle virtual controllers
|
13
13
|
|
14
14
|
@examples << example
|
15
15
|
end
|
16
16
|
|
17
|
+
def clear
|
18
|
+
@examples = []
|
19
|
+
end
|
20
|
+
|
17
21
|
def result
|
18
22
|
result = {}
|
19
23
|
@examples.map { |example| OpenStruct.new(example) }.sort_by(&:source_location)
|
20
|
-
.group_by { |ex| [ex.controller, ex.action] }
|
24
|
+
.group_by { |ex| [ex.controller, ex.action] }
|
25
|
+
.each do |_, examples|
|
21
26
|
first = examples.first
|
22
27
|
route = ::LazyApiDoc::RouteParser.new(first.controller, first.action, first.verb).route
|
23
|
-
|
28
|
+
next if route.nil? # TODO: think about adding such cases to log
|
29
|
+
|
30
|
+
doc_path = route['doc_path']
|
24
31
|
result[doc_path] ||= {}
|
25
32
|
result[doc_path].merge!(example_group(first, examples, route))
|
26
33
|
end
|
27
34
|
result
|
28
35
|
end
|
29
36
|
|
37
|
+
private
|
38
|
+
|
30
39
|
def example_group(example, examples, route) # rubocop:disable Metrics/AbcSize
|
31
40
|
{
|
32
41
|
example['verb'].downcase => {
|
@@ -35,13 +44,13 @@ module LazyApiDoc
|
|
35
44
|
"summary" => example.action,
|
36
45
|
"parameters" => path_params(route, examples) + query_params(examples),
|
37
46
|
"requestBody" => body_params(route, examples),
|
38
|
-
"responses" => examples.group_by { |ex| ex.response[
|
47
|
+
"responses" => examples.group_by { |ex| ex.response['code'] }.map do |code, variants|
|
39
48
|
[
|
40
49
|
code,
|
41
50
|
{
|
42
51
|
"description" => variants.first["description"].capitalize,
|
43
52
|
"content" => {
|
44
|
-
example.response[
|
53
|
+
example.response['content_type'] => {
|
45
54
|
"schema" => ::LazyApiDoc::VariantsParser.new(variants.map { |v| parse_body(v.response) }).result
|
46
55
|
}
|
47
56
|
}
|
@@ -53,17 +62,17 @@ module LazyApiDoc
|
|
53
62
|
end
|
54
63
|
|
55
64
|
def parse_body(response)
|
56
|
-
if response[
|
57
|
-
JSON.parse(response[
|
65
|
+
if response['content_type'].match?("json")
|
66
|
+
JSON.parse(response['body'])
|
58
67
|
else
|
59
68
|
"Not a JSON response"
|
60
69
|
end
|
61
70
|
rescue JSON::ParserError
|
62
|
-
response[
|
71
|
+
response['body']
|
63
72
|
end
|
64
73
|
|
65
74
|
def path_params(route, examples)
|
66
|
-
path_variants = examples.map { |example| example.params.slice(*route[
|
75
|
+
path_variants = examples.map { |example| example.params.slice(*route['path_params']) }
|
67
76
|
::LazyApiDoc::VariantsParser.new(path_variants).result["properties"].map do |param_name, schema|
|
68
77
|
{
|
69
78
|
'in' => "path",
|
@@ -76,12 +85,10 @@ module LazyApiDoc
|
|
76
85
|
|
77
86
|
def query_params(examples)
|
78
87
|
query_variants = examples.map do |example|
|
79
|
-
|
80
|
-
next {}
|
88
|
+
_path, query = example.request['full_path'].split('?')
|
89
|
+
next {} unless query
|
81
90
|
|
82
|
-
|
83
|
-
full_path.last.split('&').map { |part| part.split('=').map { |each| CGI.unescape(each) } }.group_by(&:first)
|
84
|
-
.transform_values { |v| v.map(&:last) }.map { |k, v| [k, k.match?(/\[\]\z/) ? v : v.first] }.to_h
|
91
|
+
CGI.parse(query).map { |k, v| [k.gsub('[]', ''), k.match?('\[\]') ? v : v.first] }.to_h
|
85
92
|
end
|
86
93
|
|
87
94
|
parsed = ::LazyApiDoc::VariantsParser.new(query_variants).result
|
@@ -99,7 +106,7 @@ module LazyApiDoc
|
|
99
106
|
first = examples.first
|
100
107
|
return unless %w[POST PATCH].include?(first['verb'])
|
101
108
|
|
102
|
-
variants = examples.map { |example| example.params.except("controller", "action", *route[
|
109
|
+
variants = examples.map { |example| example.params.except("controller", "action", "format", *route['path_params']) }
|
103
110
|
{
|
104
111
|
'content' => {
|
105
112
|
first.content_type => {
|
@@ -9,7 +9,7 @@ module LazyApiDoc
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def route
|
12
|
-
self.class.routes.find { |r| r[
|
12
|
+
self.class.routes.find { |r| r['action'] == action && r['controller'] == controller && r['verb'].include?(verb) }
|
13
13
|
end
|
14
14
|
|
15
15
|
def self.routes
|
@@ -22,11 +22,11 @@ module LazyApiDoc
|
|
22
22
|
route = ActionDispatch::Routing::RouteWrapper.new(route)
|
23
23
|
|
24
24
|
{
|
25
|
-
doc_path
|
26
|
-
path_params
|
27
|
-
controller
|
28
|
-
action
|
29
|
-
verb
|
25
|
+
'doc_path' => route.path.gsub("(.:format)", "").gsub(/(:\w+)/, '{\1}').delete(":"),
|
26
|
+
'path_params' => route.path.gsub("(.:format)", "").scan(/:\w+/).map { |p| p.delete(":") },
|
27
|
+
'controller' => route.controller,
|
28
|
+
'action' => route.action,
|
29
|
+
'verb' => route.verb.split('|')
|
30
30
|
}
|
31
31
|
end
|
32
32
|
end
|
@@ -73,10 +73,10 @@ module LazyApiDoc
|
|
73
73
|
result["properties"] = variant.map do |key, val|
|
74
74
|
[
|
75
75
|
key.to_s,
|
76
|
-
parse(val, variants.
|
76
|
+
parse(val, variants.select { |v| v.is_a?(Hash) }.map { |v| v.fetch(key, OPTIONAL) })
|
77
77
|
]
|
78
78
|
end.to_h
|
79
|
-
result["required"] = variant.keys.select { |key| variants.
|
79
|
+
result["required"] = variant.keys.select { |key| variants.select { |v| v.is_a?(Hash) }.all? { |v| v.key?(key) } }
|
80
80
|
result
|
81
81
|
end
|
82
82
|
|
data/lib/lazy_api_doc/version.rb
CHANGED
data/lib/lazy_api_doc.rb
CHANGED
@@ -7,60 +7,101 @@ require "yaml"
|
|
7
7
|
module LazyApiDoc
|
8
8
|
class Error < StandardError; end
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
end
|
10
|
+
class << self
|
11
|
+
attr_accessor :path, :example_file_ttl
|
13
12
|
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
def configure
|
14
|
+
yield self
|
15
|
+
end
|
17
16
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
action: example.request.params[:action],
|
22
|
-
description: example.class.description,
|
23
|
-
source_location: [example.class.metadata[:file_path], example.class.metadata[:line_number]],
|
24
|
-
verb: example.request.method,
|
25
|
-
params: example.request.params,
|
26
|
-
content_type: example.request.content_type.to_s,
|
27
|
-
request: {
|
28
|
-
query_params: example.request.query_parameters,
|
29
|
-
full_path: example.request.fullpath
|
30
|
-
},
|
31
|
-
response: {
|
32
|
-
code: example.response.status,
|
33
|
-
content_type: example.response.content_type.to_s,
|
34
|
-
body: example.response.body
|
35
|
-
}
|
36
|
-
)
|
37
|
-
end
|
17
|
+
def reset!
|
18
|
+
config_file = './config/lazy_api_doc.yml'
|
19
|
+
config = File.exist?(config_file) ? YAML.safe_load(ERB.new(File.read(config_file)).result) : {}
|
38
20
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
full_path: example.request.fullpath
|
51
|
-
},
|
52
|
-
response: {
|
53
|
-
code: example.response.status,
|
54
|
-
content_type: example.response.content_type.to_s,
|
55
|
-
body: example.response.body
|
56
|
-
}
|
57
|
-
)
|
58
|
-
end
|
21
|
+
self.path = ENV['LAZY_API_DOC_PATH'] || config['path'] || 'public/lazy_api_doc'
|
22
|
+
self.example_file_ttl = ENV['LAZY_API_DOC_EXAMPLE_FILE_TTL'] || config['example_file_ttl'] || 1800 # 30 minutes
|
23
|
+
end
|
24
|
+
|
25
|
+
def generator
|
26
|
+
@generator ||= Generator.new
|
27
|
+
end
|
28
|
+
|
29
|
+
def add(lazy_example)
|
30
|
+
generator.add(lazy_example)
|
31
|
+
end
|
59
32
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
33
|
+
def add_spec(rspec_example) # rubocop:disable Metrics/AbcSize
|
34
|
+
add(
|
35
|
+
'controller' => rspec_example.instance_variable_get(:@request).params[:controller],
|
36
|
+
'action' => rspec_example.instance_variable_get(:@request).params[:action],
|
37
|
+
'description' => rspec_example.class.description,
|
38
|
+
'source_location' => [rspec_example.class.metadata[:file_path], rspec_example.class.metadata[:line_number]],
|
39
|
+
'verb' => rspec_example.instance_variable_get(:@request).method,
|
40
|
+
'params' => rspec_example.instance_variable_get(:@request).params,
|
41
|
+
'content_type' => rspec_example.instance_variable_get(:@request).content_type.to_s,
|
42
|
+
'request' => {
|
43
|
+
'query_params' => rspec_example.instance_variable_get(:@request).query_parameters,
|
44
|
+
'full_path' => rspec_example.instance_variable_get(:@request).fullpath
|
45
|
+
},
|
46
|
+
'response' => {
|
47
|
+
'code' => rspec_example.response.status,
|
48
|
+
'content_type' => rspec_example.response.content_type.to_s,
|
49
|
+
'body' => rspec_example.response.body
|
50
|
+
}
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
def add_test(mini_test_example) # rubocop:disable Metrics/AbcSize
|
55
|
+
add(
|
56
|
+
'controller' => mini_test_example.instance_variable_get(:@request).params[:controller],
|
57
|
+
'action' => mini_test_example.instance_variable_get(:@request).params[:action],
|
58
|
+
'description' => mini_test_example.name.gsub(/\Atest_/, '').humanize,
|
59
|
+
'source_location' => mini_test_example.method(mini_test_example.name).source_location,
|
60
|
+
'verb' => mini_test_example.instance_variable_get(:@request).method,
|
61
|
+
'params' => mini_test_example.instance_variable_get(:@request).params,
|
62
|
+
'content_type' => mini_test_example.instance_variable_get(:@request).content_type.to_s,
|
63
|
+
'request' => {
|
64
|
+
'query_params' => mini_test_example.instance_variable_get(:@request).query_parameters,
|
65
|
+
'full_path' => mini_test_example.instance_variable_get(:@request).fullpath
|
66
|
+
},
|
67
|
+
'response' => {
|
68
|
+
'code' => mini_test_example.response.status,
|
69
|
+
'content_type' => mini_test_example.response.content_type.to_s,
|
70
|
+
'body' => mini_test_example.response.body
|
71
|
+
}
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
def generate_documentation
|
76
|
+
layout = YAML.safe_load(File.read("#{path}/layout.yml"))
|
77
|
+
layout["paths"] ||= {}
|
78
|
+
layout["paths"].merge!(generator.result)
|
79
|
+
File.write("#{path}/api.yml", layout.to_yaml)
|
80
|
+
end
|
81
|
+
|
82
|
+
def save_examples(process_name)
|
83
|
+
FileUtils.mkdir("#{path}/examples") unless File.exist?("#{path}/examples")
|
84
|
+
File.write(
|
85
|
+
"#{path}/examples/#{process_name}_#{ENV['TEST_ENV_NUMBER'] || SecureRandom.uuid}.json",
|
86
|
+
{
|
87
|
+
created_at: Time.now.to_i,
|
88
|
+
examples: generator.examples
|
89
|
+
}.to_json
|
90
|
+
)
|
91
|
+
end
|
92
|
+
|
93
|
+
def load_examples
|
94
|
+
valid_time = Time.now.to_i - example_file_ttl
|
95
|
+
examples = Dir["#{path}/examples/*.json"].flat_map do |file|
|
96
|
+
meta = JSON.parse(File.read(file))
|
97
|
+
next [] if meta['created_at'] < valid_time # do not handle outdated files
|
98
|
+
|
99
|
+
meta['examples']
|
100
|
+
end
|
101
|
+
generator.clear
|
102
|
+
examples.each { |example| add(example) }
|
103
|
+
end
|
65
104
|
end
|
66
105
|
end
|
106
|
+
|
107
|
+
LazyApiDoc.reset!
|
data/screenshot.png
ADDED
Binary file
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lazy_api_doc
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bogdan Guban
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-02
|
11
|
+
date: 2022-06-02 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: The gem collects all requests and responses from your request specs and
|
14
14
|
generates documentationbased on it
|
@@ -32,6 +32,9 @@ files:
|
|
32
32
|
- Rakefile
|
33
33
|
- bin/console
|
34
34
|
- bin/setup
|
35
|
+
- docs/example/api.yml
|
36
|
+
- docs/example/index.html
|
37
|
+
- docs/example/layout.yml
|
35
38
|
- lazy_api_doc.gemspec
|
36
39
|
- lib/generators/lazy_api_doc/install_generator.rb
|
37
40
|
- lib/generators/lazy_api_doc/templates/public/index.html
|
@@ -43,6 +46,7 @@ files:
|
|
43
46
|
- lib/lazy_api_doc/route_parser.rb
|
44
47
|
- lib/lazy_api_doc/variants_parser.rb
|
45
48
|
- lib/lazy_api_doc/version.rb
|
49
|
+
- screenshot.png
|
46
50
|
homepage: https://github.com/bguban/lazy_api_doc
|
47
51
|
licenses:
|
48
52
|
- MIT
|