dox 1.3.0 → 2.0.0.beta1

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.
@@ -1,16 +1,17 @@
1
1
  module Dox
2
2
  module Entities
3
3
  class Action
4
- attr_reader :name, :desc, :verb, :path, :uri_params
4
+ attr_reader :name, :desc, :verb, :path, :resource, :params
5
5
  attr_accessor :examples
6
6
 
7
- def initialize(name, details, request)
7
+ def initialize(details, request)
8
8
  @request = request
9
- @name = 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
- @uri_params = details[:action_params] || template_path_params
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("?").first
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
- @path_params ||=
34
- request.path_parameters.symbolize_keys.except(:action, :controller, :format, :subdomain)
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 template_path_params
38
- h = {}
39
- path_params.each do |param, value|
40
- param_type = guess_param_type(value)
41
- h[param] = { type: param_type, required: :required, value: value }
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)
@@ -3,6 +3,8 @@ 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
10
  def_delegator :request, :content_type, :request_content_type
@@ -10,6 +12,10 @@ module Dox
10
12
 
11
13
  def initialize(details, request, response)
12
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]
13
19
  @request = request
14
20
  @response = response
15
21
  end
@@ -34,6 +40,10 @@ module Dox
34
40
  @request_headers ||= filter_headers(request)
35
41
  end
36
42
 
43
+ def response_success?
44
+ response.successful?
45
+ end
46
+
37
47
  # Rails 4 includes the body params in the request_fullpath
38
48
  def request_fullpath
39
49
  if request.query_parameters.present?
@@ -67,7 +77,7 @@ module Dox
67
77
  request.path.presence || request.fullpath.split('?')[0]
68
78
  end
69
79
 
70
- attr_reader :desc, :request, :response
80
+ attr_reader :request, :response
71
81
 
72
82
  def filter_headers(obj)
73
83
  headers_whitelist.map do |header|
@@ -1,17 +1,16 @@
1
1
  module Dox
2
2
  module Entities
3
3
  class Resource
4
-
5
- attr_reader :name, :desc, :endpoint
4
+ attr_reader :name, :desc, :endpoint, :group
6
5
  attr_accessor :actions
7
6
 
8
- def initialize(name, details)
9
- @name = name
7
+ def initialize(details)
8
+ @name = details[:resource_name]
9
+ @group = details[:resource_group_name]
10
10
  @desc = details[:resource_desc]
11
11
  @endpoint = details[:resource_endpoint]
12
12
  @actions = {}
13
13
  end
14
-
15
14
  end
16
15
  end
17
16
  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(name, details)
9
- @name = 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
- $stderr.puts(notification.fully_formatted_failed_examples)
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
- $stderr.puts(summary.fully_formatted)
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(group_name,
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 = current_example.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 = current_example.action_name
60
- resource.actions[action_name] ||= Entities::Action.new(action_name, current_example.metadata,
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, current_example.request, current_example.response)
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
@@ -3,44 +3,35 @@ module Dox
3
3
  class ActionPrinter < BasePrinter
4
4
  def print(action)
5
5
  self.action = action
6
- @output.puts action_title
7
- @output.puts action_uri_params if action.uri_params.present?
6
+ @action_hash = find_or_add(find_or_add(spec, action.path.to_s), action.verb.downcase.to_sym)
8
7
 
9
- action.examples.each do |example|
10
- example_printer.print(example)
11
- end
8
+ add_action
9
+ add_action_params
10
+
11
+ print_examples
12
12
  end
13
13
 
14
14
  private
15
15
 
16
- attr_accessor :action
17
-
18
- def action_title
19
- <<-HEREDOC
16
+ attr_accessor :action, :action_hash
20
17
 
21
- ### #{action.name} [#{action.verb.upcase} #{action.path}]
22
- #{print_desc(action.desc)}
23
- HEREDOC
18
+ def add_action
19
+ action_hash['summary'] = action.name
20
+ action_hash['tags'] = [action.resource]
21
+ action_hash['description'] = format_desc(action.desc)
24
22
  end
25
23
 
26
- def action_uri_params
27
- <<-HEREDOC
28
- + Parameters
29
- #{formatted_params(action.uri_params)}
30
- HEREDOC
31
- end
24
+ def add_action_params
25
+ return unless action.params.present?
32
26
 
33
- def example_printer
34
- @example_printer ||= ExamplePrinter.new(@output)
27
+ action_hash['parameters'] = action.params
35
28
  end
36
29
 
37
- def formatted_params(uri_params)
38
- uri_params.map do |param, details|
39
- desc = " + #{CGI.escape(param.to_s)}: `#{CGI.escape(details[:value].to_s)}` (#{details[:type]}, #{details[:required]})"
40
- desc += " - #{details[:description]}" if details[:description].present?
41
- desc += "\n + Default: #{details[:default]}" if details[:default].present?
42
- desc
43
- end.flatten.join("\n")
30
+ def print_examples
31
+ action.examples.each do |example|
32
+ ExampleRequestPrinter.new(action_hash).print(example)
33
+ ExampleResponsePrinter.new(action_hash).print(example)
34
+ end
44
35
  end
45
36
  end
46
37
  end
@@ -1,37 +1,56 @@
1
+ require 'rexml/document'
2
+
1
3
  module Dox
2
4
  module Printers
3
5
  class BasePrinter
4
- def initialize(output)
5
- @output = output
6
+ attr_reader :spec
7
+
8
+ def initialize(spec)
9
+ @spec = spec || {}
6
10
  end
7
11
 
8
12
  def print
9
13
  raise NotImplementedError
10
14
  end
11
15
 
12
- private
16
+ def find_or_add(hash, key, default = {})
17
+ return hash[key] if hash.key?(key)
13
18
 
14
- def descriptions_folder_path
15
- Dox.config.desc_folder_path
19
+ hash[key] = default
16
20
  end
17
21
 
18
- def print_desc(desc, fullpath = false)
19
- return if desc.blank?
22
+ def read_file(path, root_path: Dox.config.descriptions_location)
23
+ return '' unless root_path
24
+
25
+ File.read(File.join(root_path, path))
26
+ end
20
27
 
21
- if desc.to_s =~ /.*\.md$/
22
- path = if fullpath
23
- desc
24
- else
25
- descriptions_folder_path.join(desc).to_s
26
- end
27
- content(path)
28
+ def formatted_body(body_str, content_type)
29
+ case content_type
30
+ when %r{application\/.*json}
31
+ JSON.parse(body_str)
32
+ when /xml/
33
+ pretty_xml(body_str)
28
34
  else
29
- desc
35
+ body_str
30
36
  end
31
37
  end
32
38
 
33
- def content(path)
34
- File.read(path)
39
+ def pretty_xml(xml_string)
40
+ doc = REXML::Document.new(xml_string)
41
+ formatter = REXML::Formatters::Pretty.new
42
+ formatter.compact = true
43
+ result = ''
44
+ formatter.write(doc, result)
45
+ result
46
+ end
47
+
48
+ def format_desc(description)
49
+ desc = description
50
+ desc = '' if desc.nil?
51
+ desc = read_file(desc) if desc.end_with?('.md')
52
+
53
+ desc
35
54
  end
36
55
  end
37
56
  end
@@ -1,26 +1,46 @@
1
1
  module Dox
2
2
  module Printers
3
3
  class DocumentPrinter < BasePrinter
4
+ def initialize(output)
5
+ super(body)
6
+ @output = output
7
+ end
8
+
4
9
  def print(passed_examples)
5
- print_meta_info
10
+ spec['paths'] = {}
11
+ spec['tags'] = []
12
+ spec['x-tagGroups'] = []
6
13
 
7
14
  passed_examples.sort.each do |_, resource_group|
8
15
  group_printer.print(resource_group)
9
16
  end
17
+
18
+ @output.puts(JSON.pretty_generate(spec))
10
19
  end
11
20
 
12
21
  private
13
22
 
14
- def group_printer
15
- @group_printer ||= ResourceGroupPrinter.new(@output)
23
+ def body
24
+ {
25
+ openapi: Dox.config.openapi_version || '3.0.0',
26
+ info: {
27
+ title: Dox.config.title || 'API Documentation',
28
+ description: adjust_description(Dox.config.description || ''),
29
+ version: Dox.config.api_version || '1.0'
30
+ }
31
+ }
16
32
  end
17
33
 
18
- def print_meta_info
19
- @output.puts(print_desc(api_desc_path))
34
+ def adjust_description(description)
35
+ description.end_with?('.md') ? acquire_desc(description) : description
20
36
  end
21
37
 
22
- def api_desc_path
23
- Dox.config.header_file_path
38
+ def acquire_desc(path)
39
+ read_file(path)
40
+ end
41
+
42
+ def group_printer
43
+ @group_printer ||= ResourceGroupPrinter.new(spec)
24
44
  end
25
45
  end
26
46
  end
@@ -0,0 +1,69 @@
1
+ module Dox
2
+ module Printers
3
+ class ExampleRequestPrinter < BasePrinter
4
+ def print(example)
5
+ self.example = example
6
+ add_example_request
7
+ end
8
+
9
+ private
10
+
11
+ attr_accessor :example
12
+
13
+ def add_example_request
14
+ spec['parameters'] = add_new_header_params(find_or_add(spec, 'parameters', []))
15
+ return if example.request_body.empty?
16
+
17
+ add_content(find_or_add(spec, 'requestBody'))
18
+ end
19
+
20
+ def add_content(body)
21
+ add_content_name(body['content'] = find_or_add(body, 'content'))
22
+ end
23
+
24
+ def add_content_name(body)
25
+ req_header = find_headers(example.request_headers)
26
+ add_example(body[req_header] = find_or_add(body, req_header))
27
+ add_schema(body[req_header], Dox.config.schema_request_folder_path)
28
+ end
29
+
30
+ def add_example(body)
31
+ add_desc(body['examples'] = find_or_add(body, 'examples'))
32
+ end
33
+
34
+ def add_desc(body)
35
+ body[example.desc] = { 'summary' => example.desc,
36
+ 'value' => formatted_body(example.request_body,
37
+ example.request_content_type) }
38
+ end
39
+
40
+ def add_schema(body, path)
41
+ return if example.request_schema.nil?
42
+ return unless path
43
+
44
+ file_path = File.join(path, "#{example.request_schema}.json")
45
+
46
+ body['schema'] = File.file?(file_path) ? { '$ref' => file_path } : JSON.parse(example.request_schema)
47
+ end
48
+
49
+ def find_headers(headers)
50
+ headers.find { |key, _| key == 'Accept' }&.last || 'any'
51
+ end
52
+
53
+ def acquire_header_params
54
+ example.request_headers.map do |key, value|
55
+ { name: key, in: :header, example: value }
56
+ end
57
+ end
58
+
59
+ def add_new_header_params(header_params)
60
+ example.request_headers.each do |key, value|
61
+ header_params.push(name: key, in: :header, example: value) unless
62
+ header_params.detect { |hash| hash[:name] == key }
63
+ end
64
+
65
+ header_params
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,86 @@
1
+ module Dox
2
+ module Printers
3
+ class ExampleResponsePrinter < BasePrinter
4
+ def print(example)
5
+ self.example = example
6
+ add_example_response
7
+ end
8
+
9
+ private
10
+
11
+ attr_accessor :example
12
+
13
+ def add_example_response
14
+ add_statuses(find_or_add(find_or_add(spec, 'responses'), example.response_status.to_s))
15
+ end
16
+
17
+ def add_statuses(body)
18
+ add_status_desc(body)
19
+ add_content(body)
20
+ add_headers(body)
21
+ end
22
+
23
+ def add_status_desc(body)
24
+ body['description'] = Util::Http::HTTP_STATUS_CODES[example.response_status]
25
+ end
26
+
27
+ def add_content(body)
28
+ add_content_name(body['content'] = find_or_add(body, 'content'))
29
+ end
30
+
31
+ def add_content_name(body)
32
+ resp_header = find_headers(example.response_headers)
33
+
34
+ add_example(body[resp_header] = find_or_add(body, resp_header))
35
+ add_schema(body[resp_header], Dox.config.schema_response_folder_path)
36
+ end
37
+
38
+ def add_example(body)
39
+ return if example.response_body.empty?
40
+
41
+ add_desc(body['examples'] = find_or_add(body, 'examples'))
42
+ end
43
+
44
+ def add_desc(body)
45
+ body[example.desc] = { 'summary' => example.desc,
46
+ 'value' => formatted_body(example.response_body,
47
+ find_headers(example.response_headers)) }
48
+ end
49
+
50
+ def add_schema(body, path)
51
+ return unless path
52
+
53
+ schema = find_schema
54
+
55
+ return unless schema
56
+
57
+ add_schema_to_hash(body, path, schema)
58
+ end
59
+
60
+ def find_schema
61
+ if example.response_success?
62
+ example.response_schema_success
63
+ else
64
+ example.response_schema_fail || Dox.config.schema_response_fail_file_path
65
+ end
66
+ end
67
+
68
+ def add_schema_to_hash(body, path, schema)
69
+ body['schema'] =
70
+ if schema.is_a?(Pathname)
71
+ { '$ref' => schema }
72
+ else
73
+ { '$ref' => File.join(path, "#{schema}.json") }
74
+ end
75
+ end
76
+
77
+ def find_headers(headers)
78
+ headers.find { |key, _| key == 'Content-Type' }&.last || 'any'
79
+ end
80
+
81
+ def add_headers(body)
82
+ body['headers'] = Hash[example.response_headers.map { |key, value| [key, { description: value }] }]
83
+ end
84
+ end
85
+ end
86
+ end
@@ -3,7 +3,7 @@ module Dox
3
3
  class ResourceGroupPrinter < BasePrinter
4
4
  def print(resource_group)
5
5
  self.resource_group = resource_group
6
- @output.puts resource_group_title
6
+ add_resource_group
7
7
 
8
8
  resource_group.resources.each do |_, resource|
9
9
  resource_printer.print(resource)
@@ -14,16 +14,16 @@ module Dox
14
14
 
15
15
  attr_accessor :resource_group
16
16
 
17
- def resource_group_title
18
- <<-HEREDOC
17
+ def add_resource_group
18
+ spec['x-tagGroups'].push(name: resource_group.name, 'tags' => []) unless group_included?
19
+ end
19
20
 
20
- # Group #{resource_group.name}
21
- #{print_desc(resource_group.desc)}
22
- HEREDOC
21
+ def group_included?
22
+ spec['x-tagGroups'].find { |group| group[:name] == resource_group.name }
23
23
  end
24
24
 
25
25
  def resource_printer
26
- @resource_printer ||= ResourcePrinter.new(@output)
26
+ @resource_printer ||= ResourcePrinter.new(spec)
27
27
  end
28
28
  end
29
29
  end
@@ -3,7 +3,7 @@ module Dox
3
3
  class ResourcePrinter < BasePrinter
4
4
  def print(resource)
5
5
  self.resource = resource
6
- @output.puts resource_title
6
+ add_resources
7
7
 
8
8
  resource.actions.each do |_, action|
9
9
  action_printer.print(action)
@@ -14,16 +14,21 @@ module Dox
14
14
 
15
15
  attr_accessor :resource
16
16
 
17
- def resource_title
18
- <<-HEREDOC
17
+ def add_resources
18
+ add_to_tags
19
+ add_to_groups
20
+ end
21
+
22
+ def add_to_tags
23
+ spec['tags'] = spec['tags'].push(name: resource.name, description: format_desc(resource.desc)).uniq
24
+ end
19
25
 
20
- ## #{resource.name} [#{resource.endpoint}]
21
- #{print_desc(resource.desc)}
22
- HEREDOC
26
+ def add_to_groups
27
+ spec['x-tagGroups'].find { |group| group[:name] == resource.group }['tags'].push(resource.name)
23
28
  end
24
29
 
25
30
  def action_printer
26
- @action_printer ||= ActionPrinter.new(@output)
31
+ @action_printer ||= ActionPrinter.new(spec['paths'])
27
32
  end
28
33
  end
29
34
  end