dox 1.1.0 → 2.0.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.
@@ -0,0 +1,19 @@
1
+ module Dox
2
+ module Formatters
3
+ class Base
4
+ def initialize(http_env)
5
+ @http_env = http_env
6
+ http_env_body = http_env.body
7
+ @body = http_env_body.respond_to?(:read) ? http_env_body.read : http_env_body
8
+ end
9
+
10
+ def format
11
+ raise 'no format method defined in formatter'
12
+ end
13
+
14
+ private
15
+
16
+ attr_reader :http_env, :body
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,14 @@
1
+ module Dox
2
+ module Formatters
3
+ class Json < Dox::Formatters::Base
4
+ def format
5
+ # in cases where the body isn't valid JSON
6
+ # and the headers specify the Content-Type is application/json
7
+ # an error should be raised
8
+ return '' if body.nil? || body.length < 2
9
+
10
+ JSON.pretty_generate(JSON.parse(body))
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,23 @@
1
+ require 'rack'
2
+
3
+ module Dox
4
+ module Formatters
5
+ class Multipart
6
+ def initialize(http_env)
7
+ @http_env = http_env
8
+ end
9
+
10
+ def format
11
+ JSON.pretty_generate(extracted_multipart)
12
+ end
13
+
14
+ private
15
+
16
+ def extracted_multipart
17
+ Rack::Multipart.extract_multipart(http_env)
18
+ end
19
+
20
+ attr_reader :http_env
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,13 @@
1
+ module Dox
2
+ module Formatters
3
+ class Plain < Dox::Formatters::Base
4
+ def format
5
+ return body if body.encoding == Encoding::UTF_8
6
+
7
+ body.encode(Encoding::UTF_8)
8
+ rescue Encoding::UndefinedConversionError
9
+ "#{body.encoding} stream"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ module Dox
2
+ module Formatters
3
+ class Xml < Dox::Formatters::Base
4
+ def format
5
+ doc = REXML::Document.new(body)
6
+ formatter = REXML::Formatters::Pretty.new
7
+ formatter.compact = true
8
+ result = ''
9
+ formatter.write(doc, result)
10
+ result
11
+ end
12
+ end
13
+ end
14
+ end
@@ -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,56 @@
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
+ order_groups
19
+
20
+ @output.puts(JSON.pretty_generate(spec))
10
21
  end
11
22
 
12
23
  private
13
24
 
14
- def group_printer
15
- @group_printer ||= ResourceGroupPrinter.new(@output)
25
+ def body
26
+ {
27
+ openapi: Dox.config.openapi_version || '3.0.0',
28
+ info: {
29
+ title: Dox.config.title || 'API Documentation',
30
+ description: adjust_description(Dox.config.header_description || ''),
31
+ version: Dox.config.api_version || '1.0'
32
+ }
33
+ }
34
+ end
35
+
36
+ def adjust_description(description)
37
+ description.end_with?('.md') ? acquire_desc(description) : description
38
+ end
39
+
40
+ def acquire_desc(path)
41
+ read_file(path)
16
42
  end
17
43
 
18
- def print_meta_info
19
- @output.puts(print_desc(api_desc_path))
44
+ def group_printer
45
+ @group_printer ||= ResourceGroupPrinter.new(spec)
20
46
  end
21
47
 
22
- def api_desc_path
23
- Dox.config.header_file_path
48
+ def order_groups
49
+ return if (Dox.config.groups_order || []).empty?
50
+
51
+ spec['x-tagGroups'] = spec['x-tagGroups'].sort_by do |tag|
52
+ Dox.config.groups_order.index(tag[:name]) || 100
53
+ end
24
54
  end
25
55
  end
26
56
  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