dox 1.1.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.rubocop.yml +6 -3
- data/.ruby-version +1 -1
- data/.travis.yml +7 -11
- data/CHANGES.md +29 -0
- data/Gemfile +1 -1
- data/README.md +112 -117
- data/Rakefile +3 -3
- data/bin/console +3 -3
- data/dox.gemspec +15 -14
- data/lib/dox.rb +14 -1
- data/lib/dox/config.rb +39 -5
- data/lib/dox/dsl/action.rb +10 -4
- data/lib/dox/dsl/documentation.rb +4 -0
- data/lib/dox/dsl/resource.rb +0 -2
- data/lib/dox/dsl/syntax.rb +1 -0
- data/lib/dox/entities/action.rb +33 -13
- data/lib/dox/entities/example.rb +33 -3
- data/lib/dox/entities/resource.rb +4 -6
- data/lib/dox/entities/resource_group.rb +2 -3
- data/lib/dox/formatter.rb +10 -11
- data/lib/dox/formatters/base.rb +19 -0
- data/lib/dox/formatters/json.rb +14 -0
- data/lib/dox/formatters/multipart.rb +23 -0
- data/lib/dox/formatters/plain.rb +13 -0
- data/lib/dox/formatters/xml.rb +14 -0
- data/lib/dox/printers/action_printer.rb +18 -27
- data/lib/dox/printers/base_printer.rb +36 -17
- data/lib/dox/printers/document_printer.rb +37 -7
- data/lib/dox/printers/example_request_printer.rb +69 -0
- data/lib/dox/printers/example_response_printer.rb +86 -0
- data/lib/dox/printers/resource_group_printer.rb +7 -7
- data/lib/dox/printers/resource_printer.rb +12 -7
- data/lib/dox/util/http.rb +64 -0
- data/lib/dox/version.rb +1 -1
- metadata +93 -32
- data/lib/dox/printers/example_printer.rb +0 -132
data/Rakefile
CHANGED
data/bin/console
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'dox'
|
5
5
|
|
6
6
|
# You can add fixtures and/or initialization code here to make experimenting
|
7
7
|
# with your gem easier. You can also use a different console, if you like.
|
@@ -10,5 +10,5 @@ require "dox"
|
|
10
10
|
# require "pry"
|
11
11
|
# Pry.start
|
12
12
|
|
13
|
-
require
|
13
|
+
require 'irb'
|
14
14
|
IRB.start
|
data/dox.gemspec
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
1
|
+
lib = File.expand_path('lib', __dir__)
|
3
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
3
|
require 'dox/version'
|
5
4
|
|
@@ -9,31 +8,33 @@ Gem::Specification.new do |spec|
|
|
9
8
|
spec.authors = ['Melita Kokot', 'Vedran Hrnčić']
|
10
9
|
spec.email = ['melita.kokot@gmail.com', 'vrabac266@gmail.com']
|
11
10
|
|
12
|
-
spec.summary = 'Generates API documentation for rspec in
|
11
|
+
spec.summary = 'Generates API documentation for rspec in OpenAPI format.'
|
13
12
|
spec.homepage = 'https://github.com/infinum/dox'
|
14
13
|
spec.license = 'MIT'
|
15
14
|
|
16
|
-
spec.required_ruby_version = '>=2.0.0'
|
15
|
+
spec.required_ruby_version = '>= 2.0.0'
|
17
16
|
|
18
17
|
# Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
|
19
18
|
# delete this section to allow pushing this gem to any host.
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
raise 'RubyGems 2.0 or newer is required to protect against public gem pushes.'
|
24
|
-
end
|
19
|
+
raise 'RubyGems 2.0 or newer is required to protect against public gem pushes.' unless spec.respond_to?(:metadata)
|
20
|
+
|
21
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
25
22
|
|
26
23
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
27
24
|
spec.bindir = 'exe'
|
28
25
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
29
26
|
spec.require_paths = ['lib']
|
30
27
|
|
31
|
-
spec.add_development_dependency 'bundler'
|
32
|
-
spec.add_development_dependency '
|
28
|
+
spec.add_development_dependency 'bundler'
|
29
|
+
spec.add_development_dependency 'codeclimate-test-reporter'
|
30
|
+
spec.add_development_dependency 'json'
|
31
|
+
spec.add_development_dependency 'pry-nav'
|
32
|
+
spec.add_development_dependency 'pry-rails'
|
33
|
+
spec.add_development_dependency 'pry-stack_explorer'
|
34
|
+
spec.add_runtime_dependency 'rails', '>= 4.0'
|
35
|
+
spec.add_development_dependency 'rake'
|
33
36
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
34
|
-
spec.add_development_dependency '
|
37
|
+
spec.add_development_dependency 'rubocop'
|
35
38
|
spec.add_development_dependency 'simplecov'
|
36
|
-
spec.add_development_dependency 'codeclimate-test-reporter'
|
37
39
|
spec.add_runtime_dependency 'rspec-core'
|
38
|
-
spec.add_runtime_dependency 'rails', '>= 4.0'
|
39
40
|
end
|
data/lib/dox.rb
CHANGED
@@ -19,10 +19,16 @@ require 'dox/errors/invalid_action_error'
|
|
19
19
|
require 'dox/errors/invalid_resource_error'
|
20
20
|
require 'dox/errors/invalid_resource_group_error'
|
21
21
|
require 'dox/formatter'
|
22
|
+
require 'dox/formatters/base'
|
23
|
+
require 'dox/formatters/json'
|
24
|
+
require 'dox/formatters/multipart'
|
25
|
+
require 'dox/formatters/plain'
|
26
|
+
require 'dox/formatters/xml'
|
22
27
|
require 'dox/printers/base_printer'
|
23
28
|
require 'dox/printers/action_printer'
|
24
29
|
require 'dox/printers/document_printer'
|
25
|
-
require 'dox/printers/
|
30
|
+
require 'dox/printers/example_request_printer'
|
31
|
+
require 'dox/printers/example_response_printer'
|
26
32
|
require 'dox/printers/resource_group_printer'
|
27
33
|
require 'dox/printers/resource_printer'
|
28
34
|
require 'dox/util/http'
|
@@ -43,4 +49,11 @@ module Dox
|
|
43
49
|
def self.full_headers_whitelist
|
44
50
|
(config.headers_whitelist.to_a + DEFAULT_HEADERS_WHITELIST).uniq
|
45
51
|
end
|
52
|
+
|
53
|
+
RSpec.configure do |config|
|
54
|
+
config.after(:each, :dox) do |example|
|
55
|
+
example.metadata[:request] = request
|
56
|
+
example.metadata[:response] = response
|
57
|
+
end
|
58
|
+
end
|
46
59
|
end
|
data/lib/dox/config.rb
CHANGED
@@ -1,16 +1,50 @@
|
|
1
1
|
module Dox
|
2
2
|
class Config
|
3
|
-
attr_reader :
|
3
|
+
attr_reader :schema_request_folder_path
|
4
|
+
attr_reader :schema_response_folder_path
|
5
|
+
attr_reader :schema_response_fail_file_path
|
4
6
|
attr_accessor :headers_whitelist
|
7
|
+
attr_accessor :openapi_version
|
8
|
+
attr_accessor :api_version
|
9
|
+
attr_accessor :title
|
10
|
+
attr_accessor :header_description
|
11
|
+
attr_accessor :groups_order
|
12
|
+
attr_reader :descriptions_location
|
5
13
|
|
6
|
-
def
|
14
|
+
def descriptions_location=(folder_path)
|
15
|
+
raise(Errors::FolderNotFoundError, folder_path) unless Dir.exist?(folder_path)
|
16
|
+
|
17
|
+
@descriptions_location = folder_path
|
18
|
+
end
|
19
|
+
|
20
|
+
def schema_request_folder_path=(folder_path)
|
21
|
+
raise(Errors::FolderNotFoundError, folder_path) unless Dir.exist?(folder_path)
|
22
|
+
|
23
|
+
@schema_request_folder_path = folder_path
|
24
|
+
end
|
25
|
+
|
26
|
+
def schema_response_folder_path=(folder_path)
|
27
|
+
raise(Errors::FolderNotFoundError, folder_path) unless Dir.exist?(folder_path)
|
28
|
+
|
29
|
+
@schema_response_folder_path = folder_path
|
30
|
+
end
|
31
|
+
|
32
|
+
def schema_response_fail_file_path=(file_path)
|
7
33
|
raise(Errors::FileNotFoundError, file_path) unless File.exist?(file_path)
|
8
|
-
|
34
|
+
|
35
|
+
@schema_response_fail_file_path = file_path
|
9
36
|
end
|
10
37
|
|
11
38
|
def desc_folder_path=(folder_path)
|
12
|
-
|
13
|
-
|
39
|
+
warn(
|
40
|
+
'DEPRECATION WARNING: desc_folder_path will be removed in the next release, please use descriptions_location instead' # rubocop:disable Layout/LineLength
|
41
|
+
)
|
42
|
+
|
43
|
+
self.descriptions_location = folder_path
|
44
|
+
end
|
45
|
+
|
46
|
+
def header_file_path=(_file_path)
|
47
|
+
warn('WARNING: header_file_path is no longer used. Move header description to config.header_description.')
|
14
48
|
end
|
15
49
|
end
|
16
50
|
end
|
data/lib/dox/dsl/action.rb
CHANGED
@@ -8,6 +8,10 @@ module Dox
|
|
8
8
|
attr_writer :path
|
9
9
|
attr_writer :desc
|
10
10
|
attr_writer :params
|
11
|
+
attr_writer :query_params
|
12
|
+
attr_writer :request_schema
|
13
|
+
attr_writer :response_schema_success
|
14
|
+
attr_writer :response_schema_fail
|
11
15
|
|
12
16
|
def initialize(name, &block)
|
13
17
|
self.name = name
|
@@ -17,13 +21,15 @@ module Dox
|
|
17
21
|
end
|
18
22
|
|
19
23
|
def config
|
20
|
-
{
|
24
|
+
{ action_request_schema: @request_schema.presence,
|
25
|
+
action_response_schema_success: @response_schema_success.presence,
|
26
|
+
action_response_schema_fail: @response_schema_fail.presence,
|
21
27
|
action_name: @name.presence,
|
22
28
|
action_verb: @verb.presence,
|
23
29
|
action_path: @path.presence,
|
24
|
-
action_desc: @desc.presence,
|
25
|
-
action_params: @params
|
26
|
-
|
30
|
+
action_desc: @desc.presence || '',
|
31
|
+
action_params: @params,
|
32
|
+
action_query_params: @query_params.presence || [] }
|
27
33
|
end
|
28
34
|
end
|
29
35
|
end
|
@@ -14,6 +14,8 @@ module Dox
|
|
14
14
|
self._resource = Resource.new(name, &block)
|
15
15
|
end
|
16
16
|
|
17
|
+
alias tag resource
|
18
|
+
|
17
19
|
def action(name, &block)
|
18
20
|
self._action = Action.new(name, &block)
|
19
21
|
end
|
@@ -22,6 +24,8 @@ module Dox
|
|
22
24
|
self._group = ResourceGroup.new(name, &block)
|
23
25
|
end
|
24
26
|
|
27
|
+
alias x_tag group
|
28
|
+
|
25
29
|
def config
|
26
30
|
{}.merge(_resource ? _resource.config : {})
|
27
31
|
.merge(_action ? _action.config : {})
|
data/lib/dox/dsl/resource.rb
CHANGED
@@ -16,7 +16,6 @@ module Dox
|
|
16
16
|
|
17
17
|
raise(Dox::Errors::InvalidResourceError, 'Resource name is required!') if @name.blank?
|
18
18
|
raise(Dox::Errors::InvalidResourceError, 'Resource group is required!') if @group.blank?
|
19
|
-
raise(Dox::Errors::InvalidResourceError, 'Resource endpoint is required!') if @endpoint.blank?
|
20
19
|
end
|
21
20
|
|
22
21
|
def config
|
@@ -24,7 +23,6 @@ module Dox
|
|
24
23
|
resource_name: @name.presence,
|
25
24
|
resource_desc: @desc.presence,
|
26
25
|
resource_group_name: @group.presence,
|
27
|
-
resource_endpoint: @endpoint.presence,
|
28
26
|
apidoc: true
|
29
27
|
}
|
30
28
|
end
|
data/lib/dox/dsl/syntax.rb
CHANGED
data/lib/dox/entities/action.rb
CHANGED
@@ -1,16 +1,17 @@
|
|
1
1
|
module Dox
|
2
2
|
module Entities
|
3
3
|
class Action
|
4
|
-
attr_reader :name, :desc, :verb, :path, :
|
4
|
+
attr_reader :name, :desc, :verb, :path, :resource, :params
|
5
5
|
attr_accessor :examples
|
6
6
|
|
7
|
-
def initialize(
|
7
|
+
def initialize(details, request)
|
8
8
|
@request = request
|
9
|
-
@name =
|
9
|
+
@name = details[:action_name]
|
10
|
+
@resource = details[:resource_name]
|
10
11
|
@desc = details[:action_desc]
|
11
12
|
@verb = details[:action_verb] || request.method
|
12
13
|
@path = details[:action_path] || template_path
|
13
|
-
@
|
14
|
+
@params = template_params(details[:action_params], details[:action_query_params] || [])
|
14
15
|
@examples = []
|
15
16
|
|
16
17
|
validate!
|
@@ -22,25 +23,44 @@ module Dox
|
|
22
23
|
|
23
24
|
# /pokemons/1 => pokemons/{id}
|
24
25
|
def template_path
|
25
|
-
path = request.path.dup.presence || request.fullpath.split(
|
26
|
+
path = request.path.dup.presence || request.fullpath.split('?').first
|
26
27
|
path_params.each do |key, value|
|
27
28
|
path.sub!(%r{\/#{value}(\/|$)}, "/{#{key}}\\1")
|
28
29
|
end
|
30
|
+
|
29
31
|
path
|
30
32
|
end
|
31
33
|
|
34
|
+
def template_params(defined_params, query_params)
|
35
|
+
acquire_path_params + acquire_defined_params(defined_params) + query_params
|
36
|
+
end
|
37
|
+
|
32
38
|
def path_params
|
33
|
-
|
34
|
-
|
39
|
+
request.path_parameters.symbolize_keys.except(:action, :controller, :format, :subdomain)
|
40
|
+
end
|
41
|
+
|
42
|
+
def acquire_path_params
|
43
|
+
return [] if path_params.nil?
|
44
|
+
|
45
|
+
path_params.map do |param, value|
|
46
|
+
{ name: param,
|
47
|
+
in: :path,
|
48
|
+
schema: { type: guess_param_type(value) },
|
49
|
+
example: value }
|
50
|
+
end
|
35
51
|
end
|
36
52
|
|
37
|
-
def
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
53
|
+
def acquire_defined_params(defined_params)
|
54
|
+
return [] if defined_params.nil?
|
55
|
+
|
56
|
+
defined_params.map do |key, value|
|
57
|
+
{ name: key,
|
58
|
+
in: 'query',
|
59
|
+
required: value[:required],
|
60
|
+
example: value[:value],
|
61
|
+
description: value[:description].presence || '',
|
62
|
+
schema: { type: value[:type] } }
|
42
63
|
end
|
43
|
-
h
|
44
64
|
end
|
45
65
|
|
46
66
|
def guess_param_type(param)
|
data/lib/dox/entities/example.rb
CHANGED
@@ -3,20 +3,29 @@ module Dox
|
|
3
3
|
class Example
|
4
4
|
extend Forwardable
|
5
5
|
|
6
|
+
attr_reader :desc, :name, :request_schema, :response_schema_success, :response_schema_fail
|
7
|
+
|
6
8
|
def_delegator :response, :status, :response_status
|
7
9
|
def_delegator :response, :content_type, :response_content_type
|
8
|
-
def_delegator :response, :body, :response_body
|
9
10
|
def_delegator :request, :content_type, :request_content_type
|
10
11
|
def_delegator :request, :method, :request_method
|
11
12
|
|
12
13
|
def initialize(details, request, response)
|
13
14
|
@desc = details[:description]
|
15
|
+
@name = details[:resource_name].downcase
|
16
|
+
@request_schema = details[:action_request_schema]
|
17
|
+
@response_schema_success = details[:action_response_schema_success]
|
18
|
+
@response_schema_fail = details[:action_response_schema_fail]
|
14
19
|
@request = request
|
15
20
|
@response = response
|
16
21
|
end
|
17
22
|
|
18
23
|
def request_body
|
19
|
-
@request_body ||= request
|
24
|
+
@request_body ||= format_content(request, request_content_type)
|
25
|
+
end
|
26
|
+
|
27
|
+
def response_body
|
28
|
+
@response_body ||= format_content(response, response_content_type)
|
20
29
|
end
|
21
30
|
|
22
31
|
def request_identifier
|
@@ -31,6 +40,10 @@ module Dox
|
|
31
40
|
@request_headers ||= filter_headers(request)
|
32
41
|
end
|
33
42
|
|
43
|
+
def response_success?
|
44
|
+
response.successful?
|
45
|
+
end
|
46
|
+
|
34
47
|
# Rails 4 includes the body params in the request_fullpath
|
35
48
|
def request_fullpath
|
36
49
|
if request.query_parameters.present?
|
@@ -42,12 +55,29 @@ module Dox
|
|
42
55
|
|
43
56
|
private
|
44
57
|
|
58
|
+
def format_content(http_env, content_type)
|
59
|
+
formatter(content_type).new(http_env).format
|
60
|
+
end
|
61
|
+
|
62
|
+
def formatter(content_type)
|
63
|
+
case content_type
|
64
|
+
when %r{application\/.*json}
|
65
|
+
Dox::Formatters::Json
|
66
|
+
when /xml/
|
67
|
+
Dox::Formatters::Xml
|
68
|
+
when /multipart/
|
69
|
+
Dox::Formatters::Multipart
|
70
|
+
else
|
71
|
+
Dox::Formatters::Plain
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
45
75
|
# Rails 5.0.2 returns "" for request.path
|
46
76
|
def request_path
|
47
77
|
request.path.presence || request.fullpath.split('?')[0]
|
48
78
|
end
|
49
79
|
|
50
|
-
attr_reader :
|
80
|
+
attr_reader :request, :response
|
51
81
|
|
52
82
|
def filter_headers(obj)
|
53
83
|
headers_whitelist.map do |header|
|
@@ -1,17 +1,15 @@
|
|
1
1
|
module Dox
|
2
2
|
module Entities
|
3
3
|
class Resource
|
4
|
-
|
5
|
-
attr_reader :name, :desc, :endpoint
|
4
|
+
attr_reader :name, :desc, :group
|
6
5
|
attr_accessor :actions
|
7
6
|
|
8
|
-
def initialize(
|
9
|
-
@name =
|
7
|
+
def initialize(details)
|
8
|
+
@name = details[:resource_name]
|
9
|
+
@group = details[:resource_group_name]
|
10
10
|
@desc = details[:resource_desc]
|
11
|
-
@endpoint = details[:resource_endpoint]
|
12
11
|
@actions = {}
|
13
12
|
end
|
14
|
-
|
15
13
|
end
|
16
14
|
end
|
17
15
|
end
|
@@ -1,12 +1,11 @@
|
|
1
1
|
module Dox
|
2
2
|
module Entities
|
3
3
|
class ResourceGroup
|
4
|
-
|
5
4
|
attr_reader :name
|
6
5
|
attr_accessor :desc, :resources
|
7
6
|
|
8
|
-
def initialize(
|
9
|
-
@name =
|
7
|
+
def initialize(details)
|
8
|
+
@name = details[:resource_group_name]
|
10
9
|
@desc = details[:resource_group_desc]
|
11
10
|
@resources = {}
|
12
11
|
end
|
data/lib/dox/formatter.rb
CHANGED
@@ -20,7 +20,7 @@ module Dox
|
|
20
20
|
|
21
21
|
def stop(notification)
|
22
22
|
if notification.failed_examples.any?
|
23
|
-
|
23
|
+
warn(notification.fully_formatted_failed_examples)
|
24
24
|
else
|
25
25
|
printer.print(passed_examples)
|
26
26
|
end
|
@@ -28,7 +28,8 @@ module Dox
|
|
28
28
|
|
29
29
|
def dump_summary(summary)
|
30
30
|
return if summary.failed_examples.none?
|
31
|
-
|
31
|
+
|
32
|
+
warn(summary.fully_formatted)
|
32
33
|
exit(-1)
|
33
34
|
end
|
34
35
|
|
@@ -44,28 +45,26 @@ module Dox
|
|
44
45
|
if group
|
45
46
|
group.tap { |g| g.desc ||= current_example.resource_group_desc }
|
46
47
|
else
|
47
|
-
passed_examples[group_name] = Entities::ResourceGroup.new(
|
48
|
-
current_example.metadata)
|
48
|
+
passed_examples[group_name] = Entities::ResourceGroup.new(current_example.metadata)
|
49
49
|
end
|
50
50
|
end
|
51
51
|
|
52
52
|
def load_or_save_resource_to_group(group)
|
53
|
-
resource_name
|
54
|
-
group.resources[resource_name] ||= Entities::Resource.new(resource_name,
|
55
|
-
current_example.metadata)
|
53
|
+
group.resources[current_example.resource_name] ||= Entities::Resource.new(current_example.metadata)
|
56
54
|
end
|
57
55
|
|
58
56
|
def load_or_save_action_to_resource(resource)
|
59
|
-
action_name
|
60
|
-
|
61
|
-
current_example.request)
|
57
|
+
resource.actions[current_example.action_name] ||= Entities::Action.new(current_example.metadata,
|
58
|
+
current_example.request)
|
62
59
|
end
|
63
60
|
|
64
61
|
def move_example_to_passed
|
65
62
|
group = load_or_save_group
|
66
63
|
resource = load_or_save_resource_to_group(group)
|
67
64
|
action = load_or_save_action_to_resource(resource)
|
68
|
-
action.examples << Entities::Example.new(current_example.metadata,
|
65
|
+
action.examples << Entities::Example.new(current_example.metadata,
|
66
|
+
current_example.request,
|
67
|
+
current_example.response)
|
69
68
|
end
|
70
69
|
|
71
70
|
def printer
|