class Rswag::Specs::SwaggerFormatter

Public Class Methods

new(output, config = Rswag::Specs.config) click to toggle source
# File lib/rswag/specs/swagger_formatter.rb, line 16
def initialize(output, config = Rswag::Specs.config)
  @output = output
  @config = config

  @output.puts 'Generating Swagger docs ...'
end

Public Instance Methods

example_group_finished(notification) click to toggle source
# File lib/rswag/specs/swagger_formatter.rb, line 23
def example_group_finished(notification)
  # NOTE: rspec 2.x support
  metadata = if RSPEC_VERSION > 2
    notification.group.metadata
  else
    notification.metadata
  end

  # !metadata[:document] won't work, since nil means we should generate
  # docs.
  return if metadata[:document] == false
  return unless metadata.key?(:response)

  swagger_doc = @config.get_swagger_doc(metadata[:swagger_doc])

  unless doc_version(swagger_doc).start_with?('2')
    # This is called multiple times per file!
    # metadata[:operation] is also re-used between examples within file
    # therefore be careful NOT to modify its content here.
    upgrade_request_type!(metadata)
    upgrade_servers!(swagger_doc)
    upgrade_oauth!(swagger_doc)
    upgrade_response_produces!(swagger_doc, metadata)
  end

  swagger_doc.deep_merge!(metadata_to_swagger(metadata))
end
stop(_notification = nil) click to toggle source
# File lib/rswag/specs/swagger_formatter.rb, line 51
def stop(_notification = nil)
  @config.swagger_docs.each do |url_path, doc|
    unless doc_version(doc).start_with?('2')
      doc[:paths]&.each_pair do |_k, v|
        v.each_pair do |_verb, value|
          is_hash = value.is_a?(Hash)
          if is_hash && value.dig(:parameters)
            schema_param = value.dig(:parameters)&.find { |p| (p[:in] == :body || p[:in] == :formData) && p[:schema] }
            mime_list = value.dig(:consumes) || doc[:consumes]
            if value && schema_param && mime_list
              value[:requestBody] = { content: {} } unless value.dig(:requestBody, :content)
              value[:requestBody][:required] = true if schema_param[:required]
              mime_list.each do |mime|
                value[:requestBody][:content][mime] = { schema: schema_param[:schema] }
              end
            end

            value[:parameters].reject! { |p| p[:in] == :body || p[:in] == :formData }
          end
          remove_invalid_operation_keys!(value)
        end
      end
    end

    file_path = File.join(@config.swagger_root, url_path)
    dirname = File.dirname(file_path)
    FileUtils.mkdir_p dirname unless File.exist?(dirname)

    File.open(file_path, 'w') do |file|
      file.write(pretty_generate(doc))
    end

    @output.puts "Swagger doc generated at #{file_path}"
  end
end

Private Instance Methods

doc_version(doc) click to toggle source
# File lib/rswag/specs/swagger_formatter.rb, line 120
def doc_version(doc)
  doc[:openapi] || doc[:swagger] || '3'
end
metadata_to_swagger(metadata) click to toggle source
# File lib/rswag/specs/swagger_formatter.rb, line 103
def metadata_to_swagger(metadata)
  response_code = metadata[:response][:code]
  response = metadata[:response].reject { |k, _v| k == :code }

  verb = metadata[:operation][:verb]
  operation = metadata[:operation]
    .reject { |k, _v| k == :verb }
    .merge(responses: { response_code => response })

  path_template = metadata[:path_item][:template]
  path_item = metadata[:path_item]
    .reject { |k, _v| k == :template }
    .merge(verb => operation)

  { paths: { path_template => path_item } }
end
pretty_generate(doc) click to toggle source
# File lib/rswag/specs/swagger_formatter.rb, line 89
def pretty_generate(doc)
  if @config.swagger_format == :yaml
    clean_doc = yaml_prepare(doc)
    YAML.dump(clean_doc)
  else # config errors are thrown in 'def swagger_format', no throw needed here
    JSON.pretty_generate(doc)
  end
end
remove_invalid_operation_keys!(value) click to toggle source
# File lib/rswag/specs/swagger_formatter.rb, line 197
def remove_invalid_operation_keys!(value)
  is_hash = value.is_a?(Hash)
  value.delete(:consumes) if is_hash && value.dig(:consumes)
  value.delete(:produces) if is_hash && value.dig(:produces)
end
upgrade_content!(mime_list, target_node) click to toggle source
# File lib/rswag/specs/swagger_formatter.rb, line 132
def upgrade_content!(mime_list, target_node)
  schema = target_node[:schema]
  return if mime_list.empty? || schema.nil?

  target_node[:content] ||= {}
  mime_list.each do |mime_type|
    # TODO upgrade to have content-type specific schema
    (target_node[:content][mime_type] ||= {}).merge!(schema: schema)
  end
end
upgrade_oauth!(swagger_doc) click to toggle source
# File lib/rswag/specs/swagger_formatter.rb, line 172
def upgrade_oauth!(swagger_doc)
  # find flow in securitySchemes (securityDefinitions will have been re-written)
  schemes = swagger_doc.dig(:components, :securitySchemes)
  return unless schemes&.any? { |_k, v| v.key?(:flow) }

  schemes.each do |name, v|
    next unless v.key?(:flow)

    ActiveSupport::Deprecation.warn("Rswag::Specs: WARNING: securityDefinitions flow is replaced in OpenAPI3! Rename to components/securitySchemes/#{name}/flows[] (in swagger_helper.rb)")
    flow = swagger_doc[:components][:securitySchemes][name].delete(:flow).to_s
    if flow == 'accessCode'
      ActiveSupport::Deprecation.warn("Rswag::Specs: WARNING: securityDefinitions accessCode is replaced in OpenAPI3! Rename to clientCredentials (in swagger_helper.rb)")
      flow = 'authorizationCode'
    end
    if flow == 'application'
      ActiveSupport::Deprecation.warn("Rswag::Specs: WARNING: securityDefinitions application is replaced in OpenAPI3! Rename to authorizationCode (in swagger_helper.rb)")
      flow = 'clientCredentials'
    end
    flow_elements = swagger_doc[:components][:securitySchemes][name].except(:type).each_with_object({}) do |(k, _v), a|
      a[k] = swagger_doc[:components][:securitySchemes][name].delete(k)
    end
    swagger_doc[:components][:securitySchemes][name].merge!(flows: { flow => flow_elements })
  end
end
upgrade_request_type!(metadata) click to toggle source
# File lib/rswag/specs/swagger_formatter.rb, line 143
def upgrade_request_type!(metadata)
  # No deprecation here as it seems valid to allow type as a shorthand
  operation_nodes = metadata[:operation][:parameters] || []
  path_nodes = metadata[:path_item][:parameters] || []
  header_node = metadata[:response][:headers] || {}

  (operation_nodes + path_nodes + [header_node]).each do |node|
    if node && node[:type] && node[:schema].nil?
      node[:schema] = { type: node[:type] }
      node.delete(:type)
    end
  end
end
upgrade_response_produces!(swagger_doc, metadata) click to toggle source
# File lib/rswag/specs/swagger_formatter.rb, line 124
def upgrade_response_produces!(swagger_doc, metadata)
  # Accept header
  mime_list = Array(metadata[:operation][:produces] || swagger_doc[:produces])
  target_node = metadata[:response]
  upgrade_content!(mime_list, target_node)
  metadata[:response].delete(:schema)
end
upgrade_servers!(swagger_doc) click to toggle source
# File lib/rswag/specs/swagger_formatter.rb, line 157
def upgrade_servers!(swagger_doc)
  return unless swagger_doc[:servers].nil? && swagger_doc.key?(:schemes)

  ActiveSupport::Deprecation.warn('Rswag::Specs: WARNING: schemes, host, and basePath are replaced in OpenAPI3! Rename to array of servers[{url}] (in swagger_helper.rb)')

  swagger_doc[:servers] = { urls: [] }
  swagger_doc[:schemes].each do |scheme|
    swagger_doc[:servers][:urls] << scheme + '://' + swagger_doc[:host] + swagger_doc[:basePath]
  end

  swagger_doc.delete(:schemes)
  swagger_doc.delete(:host)
  swagger_doc.delete(:basePath)
end
yaml_prepare(doc) click to toggle source
# File lib/rswag/specs/swagger_formatter.rb, line 98
def yaml_prepare(doc)
  json_doc = JSON.pretty_generate(doc)
  JSON.parse(json_doc)
end