class Swagalicious::SwaggerFormatter

Public Class Methods

new(output, config = nil) click to toggle source
# File lib/swagalicious/swagger_formatter.rb, line 13
def initialize(output, config = nil)
  @output = output
  @config = config

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

Public Instance Methods

config() click to toggle source
# File lib/swagalicious/swagger_formatter.rb, line 9
def config
  @config ||= Swagalicious.config
end
merge_metadata_to_document(doc, example) click to toggle source
# File lib/swagalicious/swagger_formatter.rb, line 20
def merge_metadata_to_document(doc, example)
   metadata = example.metadata
   # !metadata[:document] won"t work, since nil means we should generate
   # docs.
   return {} if metadata[:document] == false
   return {} unless metadata.key?(:response)
   # 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_servers!(doc)
   upgrade_oauth!(doc)
   upgrade_response_produces!(doc, metadata)
   upgrade_request_type!(metadata)

   doc.deep_merge!(metadata_to_swagger(metadata))
end
stop(notification = nil) click to toggle source
# File lib/swagalicious/swagger_formatter.rb, line 37
def stop(notification = nil)
  config.swagger_docs.each do |url_path, doc|
    examples = notification.examples.select { |e| e.metadata[:swagger_doc] == url_path }

    merged_doc = examples.each_with_object(doc) { |e, doc| doc = doc.deep_merge!(merge_metadata_to_document(doc, e)) }

    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(merged_doc))
    end

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

Private Instance Methods

doc_version(doc) click to toggle source
# File lib/swagalicious/swagger_formatter.rb, line 116
def doc_version(doc)
  doc[:openapi] || doc[:swagger] || "3.0.0"
end
metadata_to_swagger(metadata) click to toggle source
# File lib/swagalicious/swagger_formatter.rb, line 71
def metadata_to_swagger(metadata)
  response_code = metadata[:response][:code]
  response      = metadata[:response].reject { |k, _v| k == :code }
  examples      = response.delete(:examples) || []
  body          = response.delete(:requestBody) || {}

  examples.each do |mime_type, titles|
    titles.each do |title, example|
      next unless response[:content][mime_type]

      response[:content][mime_type][:examples] ||= {}

      response[:content][mime_type][:examples][title] ||= {}

      response[:content][mime_type][:examples][title][:value] = example
    end
  end

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

  is_hash = operation.is_a?(Hash)
  if is_hash && operation.dig(:parameters)
    schema_param = operation.dig(:parameters)&.find { |p| (p[:in] == :body || p[:in] == :formData) && p[:schema] }
    mime_list    = operation.dig(:consumes)
    if operation && schema_param && mime_list
      operation[:requestBody] = { content: {} }
      mime_list.each do |mime|
        operation[:requestBody][:content][mime] = { schema: schema_param[:schema] }.merge((body.dig(:content, mime) || {}))
      end
    end

    operation[:parameters].reject! { |p| p[:in] == :body || p[:in] == :formData }
  end

  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/swagalicious/swagger_formatter.rb, line 57
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/swagalicious/swagger_formatter.rb, line 202
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/swagalicious/swagger_formatter.rb, line 128
def upgrade_content!(mime_list, target_node)
  target_node.merge!(content: {})
  schema = target_node[:schema]
  return if mime_list.empty? || schema.nil?

  mime_list.each do |mime_type|
    # TODO upgrade to have content-type specific schema
    body = target_node
      .fetch(:body, {})
      .fetch(mime_type, {})

    target_node[:content][mime_type] = { schema: schema }.merge(body)
  end
end
upgrade_oauth!(swagger_doc) click to toggle source
# File lib/swagalicious/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)

    puts "Swagalicious: 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"
      puts "Swagalicious: WARNING: securityDefinitions accessCode is replaced in OpenAPI3! Rename to clientCredentials (in swagger_helper.rb)"
      flow = "authorizationCode"
    end

    if flow == "application"
      puts "Swagalicious: 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/swagalicious/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/swagalicious/swagger_formatter.rb, line 120
def upgrade_response_produces!(swagger_doc, metadata)
  # Accept header
  mime_list   = Array(metadata[:operation].delete(: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/swagalicious/swagger_formatter.rb, line 157
def upgrade_servers!(swagger_doc)
  return unless swagger_doc[:servers].nil? && swagger_doc.key?(:schemes)

  puts "Swagalicious: 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/swagalicious/swagger_formatter.rb, line 66
def yaml_prepare(doc)
  json_doc = JSON.pretty_generate(doc)
  JSON.parse(json_doc)
end