rspec_api_documentation 5.1.0 → 6.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.
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