rspec_api_documentation 5.1.0 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/lib/rspec_api_documentation.rb +26 -1
  3. data/lib/rspec_api_documentation/api_documentation.rb +2 -0
  4. data/lib/rspec_api_documentation/configuration.rb +11 -1
  5. data/lib/rspec_api_documentation/dsl/endpoint.rb +33 -1
  6. data/lib/rspec_api_documentation/dsl/endpoint/params.rb +19 -3
  7. data/lib/rspec_api_documentation/dsl/endpoint/set_param.rb +12 -1
  8. data/lib/rspec_api_documentation/dsl/resource.rb +23 -0
  9. data/lib/rspec_api_documentation/open_api/contact.rb +9 -0
  10. data/lib/rspec_api_documentation/open_api/example.rb +7 -0
  11. data/lib/rspec_api_documentation/open_api/header.rb +12 -0
  12. data/lib/rspec_api_documentation/open_api/headers.rb +7 -0
  13. data/lib/rspec_api_documentation/open_api/helper.rb +29 -0
  14. data/lib/rspec_api_documentation/open_api/info.rb +12 -0
  15. data/lib/rspec_api_documentation/open_api/license.rb +8 -0
  16. data/lib/rspec_api_documentation/open_api/node.rb +112 -0
  17. data/lib/rspec_api_documentation/open_api/operation.rb +18 -0
  18. data/lib/rspec_api_documentation/open_api/parameter.rb +33 -0
  19. data/lib/rspec_api_documentation/open_api/path.rb +13 -0
  20. data/lib/rspec_api_documentation/open_api/paths.rb +7 -0
  21. data/lib/rspec_api_documentation/open_api/response.rb +10 -0
  22. data/lib/rspec_api_documentation/open_api/responses.rb +9 -0
  23. data/lib/rspec_api_documentation/open_api/root.rb +21 -0
  24. data/lib/rspec_api_documentation/open_api/schema.rb +15 -0
  25. data/lib/rspec_api_documentation/open_api/security_definitions.rb +7 -0
  26. data/lib/rspec_api_documentation/open_api/security_schema.rb +14 -0
  27. data/lib/rspec_api_documentation/open_api/tag.rb +9 -0
  28. data/lib/rspec_api_documentation/views/api_blueprint_example.rb +15 -3
  29. data/lib/rspec_api_documentation/views/api_blueprint_index.rb +17 -2
  30. data/lib/rspec_api_documentation/views/markdown_example.rb +1 -1
  31. data/lib/rspec_api_documentation/views/markup_example.rb +8 -3
  32. data/lib/rspec_api_documentation/views/slate_index.rb +4 -0
  33. data/lib/rspec_api_documentation/writers/combined_json_writer.rb +1 -1
  34. data/lib/rspec_api_documentation/writers/json_iodocs_writer.rb +2 -2
  35. data/lib/rspec_api_documentation/writers/json_writer.rb +6 -6
  36. data/lib/rspec_api_documentation/writers/markdown_writer.rb +1 -1
  37. data/lib/rspec_api_documentation/writers/open_api_writer.rb +244 -0
  38. data/lib/rspec_api_documentation/writers/slate_writer.rb +2 -8
  39. data/templates/rspec_api_documentation/api_blueprint_index.mustache +6 -5
  40. data/templates/rspec_api_documentation/slate_index.mustache +8 -0
  41. metadata +57 -3
@@ -0,0 +1,18 @@
1
+ module RspecApiDocumentation
2
+ module OpenApi
3
+ class Operation < Node
4
+ add_setting :tags, :default => []
5
+ add_setting :summary
6
+ add_setting :description, :default => ''
7
+ add_setting :externalDocs
8
+ add_setting :operationId
9
+ add_setting :consumes
10
+ add_setting :produces
11
+ add_setting :parameters, :default => [], :schema => [Parameter]
12
+ add_setting :responses, :required => true, :schema => Responses
13
+ add_setting :schemes
14
+ add_setting :deprecated, :default => false
15
+ add_setting :security
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,33 @@
1
+ module RspecApiDocumentation
2
+ module OpenApi
3
+ class Parameter < Node
4
+ # Required to write example values to description of parameter when option `with_example: true` is provided
5
+ attr_accessor :value
6
+ attr_accessor :with_example
7
+
8
+ add_setting :name, :required => true
9
+ add_setting :in, :required => true
10
+ add_setting :description, :default => ''
11
+ add_setting :required, :default => lambda { |parameter| parameter.in.to_s == 'path' ? true : false }
12
+ add_setting :schema
13
+ add_setting :type
14
+ add_setting :items
15
+ add_setting :default
16
+ add_setting :minimum
17
+ add_setting :maximum
18
+ add_setting :enum
19
+
20
+ def description_with_example
21
+ str = description_without_example.dup || ''
22
+ if with_example && value
23
+ str << "\n" unless str.empty?
24
+ str << "Eg, `#{value}`"
25
+ end
26
+ str
27
+ end
28
+
29
+ alias_method :description_without_example, :description
30
+ alias_method :description, :description_with_example
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,13 @@
1
+ module RspecApiDocumentation
2
+ module OpenApi
3
+ class Path < Node
4
+ add_setting :get, :schema => Operation
5
+ add_setting :put, :schema => Operation
6
+ add_setting :post, :schema => Operation
7
+ add_setting :delete, :schema => Operation
8
+ add_setting :options, :schema => Operation
9
+ add_setting :head, :schema => Operation
10
+ add_setting :patch, :schema => Operation
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ module RspecApiDocumentation
2
+ module OpenApi
3
+ class Paths < Node
4
+ CHILD_CLASS = Path
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ module RspecApiDocumentation
2
+ module OpenApi
3
+ class Response < Node
4
+ add_setting :description, :required => true, :default => 'Successful operation'
5
+ add_setting :schema, :schema => Schema
6
+ add_setting :headers, :schema => Headers
7
+ add_setting :examples, :schema => Example
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ module RspecApiDocumentation
2
+ module OpenApi
3
+ class Responses < Node
4
+ CHILD_CLASS = Response
5
+
6
+ add_setting :default, :default => lambda { |responses| responses.existing_settings.size > 1 ? nil : Response.new }
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,21 @@
1
+ module RspecApiDocumentation
2
+ module OpenApi
3
+ class Root < Node
4
+ add_setting :swagger, :default => '2.0', :required => true
5
+ add_setting :info, :default => Info.new, :required => true, :schema => Info
6
+ add_setting :host, :default => 'localhost:3000'
7
+ add_setting :basePath
8
+ add_setting :schemes, :default => %w(http https)
9
+ add_setting :consumes, :default => %w(application/json application/xml)
10
+ add_setting :produces, :default => %w(application/json application/xml)
11
+ add_setting :paths, :default => Paths.new, :required => true, :schema => Paths
12
+ add_setting :definitions
13
+ add_setting :parameters
14
+ add_setting :responses
15
+ add_setting :securityDefinitions, :schema => SecurityDefinitions
16
+ add_setting :security
17
+ add_setting :tags, :default => [], :schema => [Tag]
18
+ add_setting :externalDocs
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,15 @@
1
+ module RspecApiDocumentation
2
+ module OpenApi
3
+ class Schema < Node
4
+ add_setting :format
5
+ add_setting :title
6
+ add_setting :description, :default => ''
7
+ add_setting :required
8
+ add_setting :enum
9
+ add_setting :type
10
+ add_setting :items
11
+ add_setting :properties
12
+ add_setting :example
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ module RspecApiDocumentation
2
+ module OpenApi
3
+ class SecurityDefinitions < Node
4
+ CHILD_CLASS = SecuritySchema
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,14 @@
1
+ module RspecApiDocumentation
2
+ module OpenApi
3
+ class SecuritySchema < Node
4
+ add_setting :type, :required => true
5
+ add_setting :description, :default => ''
6
+ add_setting :name
7
+ add_setting :in
8
+ add_setting :flow
9
+ add_setting :authorizationUrl
10
+ add_setting :tokenUrl
11
+ add_setting :scopes
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,9 @@
1
+ module RspecApiDocumentation
2
+ module OpenApi
3
+ class Tag < Node
4
+ add_setting :name, :required => true
5
+ add_setting :description, :default => ''
6
+ add_setting :externalDocs
7
+ end
8
+ end
9
+ end
@@ -1,7 +1,7 @@
1
1
  module RspecApiDocumentation
2
2
  module Views
3
3
  class ApiBlueprintExample < MarkupExample
4
- TOTAL_SPACES_INDENTATION = 8.freeze
4
+ TOTAL_SPACES_INDENTATION = 12.freeze
5
5
 
6
6
  def initialize(example, configuration)
7
7
  super
@@ -20,14 +20,14 @@ module RspecApiDocumentation
20
20
 
21
21
  def requests
22
22
  super.map do |request|
23
- request[:request_headers_text] = remove_utf8_for_json(request[:request_headers_text])
23
+ request[:request_headers_text] = remove_utf8_for_json(remove_content_type(request[:request_headers_text]))
24
24
  request[:request_headers_text] = indent(request[:request_headers_text])
25
25
  request[:request_content_type] = content_type(request[:request_headers])
26
26
  request[:request_content_type] = remove_utf8_for_json(request[:request_content_type])
27
27
  request[:request_body] = body_to_json(request, :request)
28
28
  request[:request_body] = indent(request[:request_body])
29
29
 
30
- request[:response_headers_text] = remove_utf8_for_json(request[:response_headers_text])
30
+ request[:response_headers_text] = remove_utf8_for_json(remove_content_type(request[:response_headers_text]))
31
31
  request[:response_headers_text] = indent(request[:response_headers_text])
32
32
  request[:response_content_type] = content_type(request[:response_headers])
33
33
  request[:response_content_type] = remove_utf8_for_json(request[:response_content_type])
@@ -46,6 +46,18 @@ module RspecApiDocumentation
46
46
 
47
47
  private
48
48
 
49
+ # `Content-Type` header is removed because the information would be duplicated
50
+ # since it's already present in `request[:request_content_type]`.
51
+ def remove_content_type(headers)
52
+ return unless headers
53
+ headers
54
+ .split("\n")
55
+ .reject { |header|
56
+ header.start_with?('Content-Type:')
57
+ }
58
+ .join("\n")
59
+ end
60
+
49
61
  def has_request?(metadata)
50
62
  metadata.any? do |key, value|
51
63
  [:request_body, :request_headers, :request_content_type].include?(key) && value
@@ -23,7 +23,7 @@ module RspecApiDocumentation
23
23
  {
24
24
  "has_attributes?".to_sym => attrs.size > 0,
25
25
  "has_parameters?".to_sym => params.size > 0,
26
- route: route,
26
+ route: format_route(examples[0]),
27
27
  route_name: examples[0][:route_name],
28
28
  attributes: attrs,
29
29
  parameters: params,
@@ -45,6 +45,17 @@ module RspecApiDocumentation
45
45
 
46
46
  private
47
47
 
48
+ # APIB follows the RFC 6570 to format URI templates.
49
+ # According to it, simple string expansion (used to perform variable
50
+ # expansion) should be represented by `{var}` and not by `/:var`
51
+ # For example `/posts/:id` should become `/posts/{id}`
52
+ # cf. https://github.com/apiaryio/api-blueprint/blob/format-1A/API%20Blueprint%20Specification.md#431-resource-section
53
+ # cf. https://tools.ietf.org/html/rfc6570#section-3.2.6
54
+ def format_route(example)
55
+ route_uri = example[:route_uri].gsub(/:(.*?)([.\/?{]|$)/, '{\1}\2')
56
+ "#{route_uri}#{example[:route_optionals]}"
57
+ end
58
+
48
59
  # APIB has both `parameters` and `attributes`. This generates a hash
49
60
  # with all of its properties, like name, description, required.
50
61
  # {
@@ -63,7 +74,11 @@ module RspecApiDocumentation
63
74
  .uniq { |property| property[:name] }
64
75
  .map do |property|
65
76
  properties = []
66
- properties << "required" if property[:required]
77
+ if property[:required] == true
78
+ properties << 'required'
79
+ else
80
+ properties << 'optional'
81
+ end
67
82
  properties << property[:type] if property[:type]
68
83
  if properties.count > 0
69
84
  property[:properties_description] = properties.join(", ")
@@ -1,7 +1,7 @@
1
1
  module RspecApiDocumentation
2
2
  module Views
3
3
  class MarkdownExample < MarkupExample
4
- EXTENSION = 'markdown'
4
+ EXTENSION = 'md'
5
5
 
6
6
  def initialize(example, configuration)
7
7
  super
@@ -3,6 +3,8 @@ require 'mustache'
3
3
  module RspecApiDocumentation
4
4
  module Views
5
5
  class MarkupExample < Mustache
6
+ SPECIAL_CHARS = /[<>:"\/\\|?*]/.freeze
7
+
6
8
  def initialize(example, configuration)
7
9
  @example = example
8
10
  @host = configuration.curl_host
@@ -19,12 +21,11 @@ module RspecApiDocumentation
19
21
  end
20
22
 
21
23
  def dirname
22
- resource_name.to_s.downcase.gsub(/\s+/, '_').gsub(":", "_")
24
+ sanitize(resource_name.to_s.downcase)
23
25
  end
24
26
 
25
27
  def filename
26
- special_chars = /[<>:"\/\\|?*]/
27
- basename = description.downcase.gsub(/\s+/, '_').gsub(special_chars, '')
28
+ basename = sanitize(description.downcase)
28
29
  basename = Digest::MD5.new.update(description).to_s if basename.blank?
29
30
  "#{basename}.#{extension}"
30
31
  end
@@ -87,6 +88,10 @@ module RspecApiDocumentation
87
88
  def content_type(headers)
88
89
  headers && headers.fetch("Content-Type", nil)
89
90
  end
91
+
92
+ def sanitize(name)
93
+ name.gsub(/\s+/, '_').gsub(SPECIAL_CHARS, '')
94
+ end
90
95
  end
91
96
  end
92
97
  end
@@ -1,6 +1,10 @@
1
1
  module RspecApiDocumentation
2
2
  module Views
3
3
  class SlateIndex < MarkdownIndex
4
+ def initialize(index, configuration)
5
+ super
6
+ self.template_name = "rspec_api_documentation/slate_index"
7
+ end
4
8
  end
5
9
  end
6
10
  end
@@ -7,7 +7,7 @@ module RspecApiDocumentation
7
7
  File.open(configuration.docs_dir.join("combined.json"), "w+") do |f|
8
8
  examples = []
9
9
  index.examples.each do |rspec_example|
10
- examples << Formatter.to_json(JsonExample.new(rspec_example, configuration))
10
+ examples << Formatter.to_json(JSONExample.new(rspec_example, configuration))
11
11
  end
12
12
 
13
13
  f.write "["
@@ -32,7 +32,7 @@ module RspecApiDocumentation
32
32
  end
33
33
 
34
34
  def examples
35
- @index.examples.map { |example| JsonExample.new(example, @configuration) }
35
+ @index.examples.map { |example| JsonIodocsExample.new(example, @configuration) }
36
36
  end
37
37
 
38
38
  def as_json(opts = nil)
@@ -48,7 +48,7 @@ module RspecApiDocumentation
48
48
  end
49
49
  end
50
50
 
51
- class JsonExample
51
+ class JsonIodocsExample
52
52
  def initialize(example, configuration)
53
53
  @example = example
54
54
  end
@@ -2,19 +2,19 @@ require 'rspec_api_documentation/writers/formatter'
2
2
 
3
3
  module RspecApiDocumentation
4
4
  module Writers
5
- class JsonWriter < Writer
5
+ class JSONWriter < Writer
6
6
  delegate :docs_dir, :to => :configuration
7
7
 
8
8
  def write
9
9
  File.open(docs_dir.join("index.json"), "w+") do |f|
10
- f.write Formatter.to_json(JsonIndex.new(index, configuration))
10
+ f.write Formatter.to_json(JSONIndex.new(index, configuration))
11
11
  end
12
12
  write_examples
13
13
  end
14
14
 
15
15
  def write_examples
16
16
  index.examples.each do |example|
17
- json_example = JsonExample.new(example, configuration)
17
+ json_example = JSONExample.new(example, configuration)
18
18
  FileUtils.mkdir_p(docs_dir.join(json_example.dirname))
19
19
  File.open(docs_dir.join(json_example.dirname, json_example.filename), "w+") do |f|
20
20
  f.write Formatter.to_json(json_example)
@@ -23,7 +23,7 @@ module RspecApiDocumentation
23
23
  end
24
24
  end
25
25
 
26
- class JsonIndex
26
+ class JSONIndex
27
27
  def initialize(index, configuration)
28
28
  @index = index
29
29
  @configuration = configuration
@@ -34,7 +34,7 @@ module RspecApiDocumentation
34
34
  end
35
35
 
36
36
  def examples
37
- @index.examples.map { |example| JsonExample.new(example, @configuration) }
37
+ @index.examples.map { |example| JSONExample.new(example, @configuration) }
38
38
  end
39
39
 
40
40
  def as_json(opts = nil)
@@ -61,7 +61,7 @@ module RspecApiDocumentation
61
61
  end
62
62
  end
63
63
 
64
- class JsonExample
64
+ class JSONExample
65
65
  def initialize(example, configuration)
66
66
  @example = example
67
67
  @host = configuration.curl_host
@@ -1,7 +1,7 @@
1
1
  module RspecApiDocumentation
2
2
  module Writers
3
3
  class MarkdownWriter < GeneralMarkupWriter
4
- EXTENSION = 'markdown'
4
+ EXTENSION = 'md'
5
5
 
6
6
  def markup_index_class
7
7
  RspecApiDocumentation::Views::MarkdownIndex
@@ -0,0 +1,244 @@
1
+ require 'rspec_api_documentation/writers/formatter'
2
+ require 'yaml'
3
+
4
+ module RspecApiDocumentation
5
+ module Writers
6
+ class OpenApiWriter < Writer
7
+ FILENAME = 'open_api'
8
+
9
+ delegate :docs_dir, :configurations_dir, to: :configuration
10
+
11
+ def write
12
+ File.open(docs_dir.join("#{FILENAME}.json"), 'w+') do |f|
13
+ f.write Formatter.to_json(OpenApiIndex.new(index, configuration, load_config))
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def load_config
20
+ return JSON.parse(File.read("#{configurations_dir}/open_api.json")) if File.exist?("#{configurations_dir}/open_api.json")
21
+ YAML.load_file("#{configurations_dir}/open_api.yml") if File.exist?("#{configurations_dir}/open_api.yml")
22
+ end
23
+ end
24
+
25
+ class OpenApiIndex
26
+ attr_reader :index, :configuration, :init_config
27
+
28
+ def initialize(index, configuration, init_config)
29
+ @index = index
30
+ @configuration = configuration
31
+ @init_config = init_config
32
+ end
33
+
34
+ def as_json
35
+ @specs = OpenApi::Root.new(init_config)
36
+ add_tags!
37
+ add_paths!
38
+ add_security_definitions!
39
+ specs.as_json
40
+ end
41
+
42
+ private
43
+
44
+ attr_reader :specs
45
+
46
+ def examples
47
+ index.examples.map { |example| OpenApiExample.new(example) }
48
+ end
49
+
50
+ def add_security_definitions!
51
+ security_definitions = OpenApi::SecurityDefinitions.new
52
+
53
+ arr = examples.map do |example|
54
+ example.respond_to?(:authentications) ? example.authentications : nil
55
+ end.compact
56
+
57
+ arr.each do |securities|
58
+ securities.each do |security, opts|
59
+ schema = OpenApi::SecuritySchema.new(
60
+ name: opts[:name],
61
+ description: opts[:description],
62
+ type: opts[:type],
63
+ in: opts[:in]
64
+ )
65
+ security_definitions.add_setting security, :value => schema
66
+ end
67
+ end
68
+ specs.securityDefinitions = security_definitions unless arr.empty?
69
+ end
70
+
71
+ def add_tags!
72
+ tags = {}
73
+ examples.each do |example|
74
+ tags[example.resource_name] ||= example.resource_explanation
75
+ end
76
+ specs.safe_assign_setting(:tags, [])
77
+ tags.each do |name, desc|
78
+ specs.tags << OpenApi::Tag.new(name: name, description: desc) unless specs.tags.any? { |tag| tag.name == name }
79
+ end
80
+ end
81
+
82
+ def add_paths!
83
+ specs.safe_assign_setting(:paths, OpenApi::Paths.new)
84
+ examples.each do |example|
85
+ specs.paths.add_setting example.route, :value => OpenApi::Path.new
86
+
87
+ operation = specs.paths.setting(example.route).setting(example.http_method) || OpenApi::Operation.new
88
+
89
+ operation.safe_assign_setting(:tags, [example.resource_name])
90
+ operation.safe_assign_setting(:summary, example.respond_to?(:route_summary) ? example.route_summary : '')
91
+ operation.safe_assign_setting(:description, example.respond_to?(:route_description) ? example.route_description : '')
92
+ operation.safe_assign_setting(:responses, OpenApi::Responses.new)
93
+ operation.safe_assign_setting(:parameters, extract_parameters(example))
94
+ operation.safe_assign_setting(:consumes, example.requests.map { |request| request[:request_content_type] }.compact.map { |q| q[/[^;]+/] })
95
+ operation.safe_assign_setting(:produces, example.requests.map { |request| request[:response_content_type] }.compact.map { |q| q[/[^;]+/] })
96
+ operation.safe_assign_setting(:security, example.respond_to?(:authentications) ? example.authentications.map { |(k, _)| {k => []} } : [])
97
+
98
+ process_responses(operation.responses, example)
99
+
100
+ specs.paths.setting(example.route).assign_setting(example.http_method, operation)
101
+ end
102
+ end
103
+
104
+ def process_responses(responses, example)
105
+ schema = extract_schema(example.respond_to?(:response_fields) ? example.response_fields : [])
106
+ example.requests.each do |request|
107
+ response = OpenApi::Response.new(
108
+ description: example.description,
109
+ schema: schema
110
+ )
111
+
112
+ if request[:response_headers]
113
+ response.safe_assign_setting(:headers, OpenApi::Headers.new)
114
+ request[:response_headers].each do |header, value|
115
+ response.headers.add_setting header, :value => OpenApi::Header.new('x-example-value' => value)
116
+ end
117
+ end
118
+
119
+ if /\A(?<response_content_type>[^;]+)/ =~ request[:response_content_type]
120
+ response.safe_assign_setting(:examples, OpenApi::Example.new)
121
+ response_body = JSON.parse(request[:response_body]) rescue nil
122
+ response.examples.add_setting response_content_type, :value => response_body
123
+ end
124
+ responses.add_setting "#{request[:response_status]}", :value => response
125
+ end
126
+ end
127
+
128
+ def extract_schema(fields)
129
+ schema = {type: 'object', properties: {}}
130
+
131
+ fields.each do |field|
132
+ current = schema
133
+ if field[:scope]
134
+ [*field[:scope]].each do |scope|
135
+ current[:properties][scope] ||= {type: 'object', properties: {}}
136
+ current = current[:properties][scope]
137
+ end
138
+ end
139
+ current[:properties][field[:name]] = {type: field[:type] || OpenApi::Helper.extract_type(field[:value])}
140
+ current[:properties][field[:name]][:example] = field[:value] if field[:value] && field[:with_example]
141
+ current[:properties][field[:name]][:default] = field[:default] if field[:default]
142
+ current[:properties][field[:name]][:description] = field[:description] if field[:description]
143
+
144
+ opts = {enum: field[:enum], minimum: field[:minimum], maximum: field[:maximum]}
145
+
146
+ if current[:properties][field[:name]][:type] == :array
147
+ current[:properties][field[:name]][:items] = field[:items] || OpenApi::Helper.extract_items(field[:value][0], opts)
148
+ else
149
+ opts.each { |k, v| current[:properties][field[:name]][k] = v if v }
150
+ end
151
+
152
+ current[:required] ||= [] << field[:name] if field[:required]
153
+ end
154
+
155
+ OpenApi::Schema.new(schema)
156
+ end
157
+
158
+ def extract_parameters(example)
159
+ extract_known_parameters(example.extended_parameters.select { |p| !p[:in].nil? }) +
160
+ extract_unknown_parameters(example, example.extended_parameters.select { |p| p[:in].nil? })
161
+ end
162
+
163
+ def extract_parameter(opts)
164
+ OpenApi::Parameter.new(
165
+ name: opts[:name],
166
+ in: opts[:in],
167
+ description: opts[:description],
168
+ required: opts[:required],
169
+ type: opts[:type] || OpenApi::Helper.extract_type(opts[:value]),
170
+ value: opts[:value],
171
+ with_example: opts[:with_example],
172
+ default: opts[:default],
173
+ ).tap do |elem|
174
+ if elem.type == :array
175
+ elem.items = opts[:items] || OpenApi::Helper.extract_items(opts[:value][0], { minimum: opts[:minimum], maximum: opts[:maximum], enum: opts[:enum] })
176
+ else
177
+ elem.minimum = opts[:minimum]
178
+ elem.maximum = opts[:maximum]
179
+ elem.enum = opts[:enum]
180
+ end
181
+ end
182
+ end
183
+
184
+ def extract_unknown_parameters(example, parameters)
185
+ if example.http_method == :get
186
+ parameters.map { |parameter| extract_parameter(parameter.merge(in: :query)) }
187
+ elsif parameters.any? { |parameter| !parameter[:scope].nil? }
188
+ [OpenApi::Parameter.new(
189
+ name: :body,
190
+ in: :body,
191
+ description: '',
192
+ schema: extract_schema(parameters)
193
+ )]
194
+ else
195
+ parameters.map { |parameter| extract_parameter(parameter.merge(in: :formData)) }
196
+ end
197
+ end
198
+
199
+ def extract_known_parameters(parameters)
200
+ result = parameters.select { |parameter| %w(query path header formData).include?(parameter[:in].to_s) }
201
+ .map { |parameter| extract_parameter(parameter) }
202
+
203
+ body = parameters.select { |parameter| %w(body).include?(parameter[:in].to_s) }
204
+
205
+ result.unshift(
206
+ OpenApi::Parameter.new(
207
+ name: :body,
208
+ in: :body,
209
+ description: '',
210
+ schema: extract_schema(body)
211
+ )
212
+ ) unless body.empty?
213
+
214
+ result
215
+ end
216
+ end
217
+
218
+ class OpenApiExample
219
+ def initialize(example)
220
+ @example = example
221
+ end
222
+
223
+ def method_missing(method, *args, &block)
224
+ @example.send(method, *args, &block)
225
+ end
226
+
227
+ def respond_to?(method, include_private = false)
228
+ super || @example.respond_to?(method, include_private)
229
+ end
230
+
231
+ def http_method
232
+ metadata[:method]
233
+ end
234
+
235
+ def requests
236
+ super.select { |request| request[:request_method].to_s.downcase == http_method.to_s.downcase }
237
+ end
238
+
239
+ def route
240
+ super.gsub(/:(?<parameter>[^\/]+)/, '{\k<parameter>}')
241
+ end
242
+ end
243
+ end
244
+ end