class Grape::Middleware::Formatter

Constants

CHUNKED

Public Instance Methods

after() click to toggle source
# File lib/grape/middleware/formatter.rb, line 21
def after
  return unless @app_response
  status, headers, bodies = *@app_response

  if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status)
    @app_response
  else
    build_formatted_response(status, headers, bodies)
  end
end
before() click to toggle source
# File lib/grape/middleware/formatter.rb, line 16
def before
  negotiate_content_type
  read_body_input
end
default_options() click to toggle source
# File lib/grape/middleware/formatter.rb, line 8
def default_options
  {
    default_format: :txt,
    formatters: {},
    parsers: {}
  }
end

Private Instance Methods

build_formatted_response(status, headers, bodies) click to toggle source
# File lib/grape/middleware/formatter.rb, line 34
def build_formatted_response(status, headers, bodies)
  headers = ensure_content_type(headers)

  if bodies.is_a?(Grape::Util::FileResponse)
    Grape::Util::SendfileResponse.new([], status, headers) do |resp|
      resp.body = bodies.file
    end
  else
    # Allow content-type to be explicitly overwritten
    formatter = fetch_formatter(headers, options)
    bodymap = bodies.collect { |body| formatter.call(body, env) }
    Rack::Response.new(bodymap, status, headers)
  end
rescue Grape::Exceptions::InvalidFormatter => e
  throw :error, status: 500, message: e.message
end
ensure_content_type(headers) click to toggle source

Set the content type header for the API format if it is not already present.

@param headers [Hash] @return [Hash]

# File lib/grape/middleware/formatter.rb, line 60
def ensure_content_type(headers)
  if headers[Grape::Http::Headers::CONTENT_TYPE]
    headers
  else
    headers.merge(Grape::Http::Headers::CONTENT_TYPE => content_type_for(env[Grape::Env::API_FORMAT]))
  end
end
fetch_formatter(headers, options) click to toggle source
# File lib/grape/middleware/formatter.rb, line 51
def fetch_formatter(headers, options)
  api_format = mime_types[headers[Grape::Http::Headers::CONTENT_TYPE]] || env[Grape::Env::API_FORMAT]
  Grape::Formatter.formatter_for(api_format, options)
end
format_from_extension() click to toggle source
# File lib/grape/middleware/formatter.rb, line 130
def format_from_extension
  parts = request.path.split('.')

  if parts.size > 1
    extension = parts.last
    # avoid symbol memory leak on an unknown format
    return extension.to_sym if content_type_for(extension)
  end
  nil
end
format_from_header() click to toggle source
# File lib/grape/middleware/formatter.rb, line 148
def format_from_header
  mime_array.each do |t|
    return mime_types[t] if mime_types.key?(t)
  end
  nil
end
format_from_params() click to toggle source
# File lib/grape/middleware/formatter.rb, line 141
def format_from_params
  fmt = Rack::Utils.parse_nested_query(env[Grape::Http::Headers::QUERY_STRING])[Grape::Http::Headers::FORMAT]
  # avoid symbol memory leak on an unknown format
  return fmt.to_sym if content_type_for(fmt)
  fmt
end
mime_array() click to toggle source
# File lib/grape/middleware/formatter.rb, line 155
def mime_array
  accept = env[Grape::Http::Headers::HTTP_ACCEPT]
  return [] unless accept

  accept_into_mime_and_quality = %r{
    (
      \w+/[\w+.-]+)     # eg application/vnd.example.myformat+xml
    (?:
     (?:;[^,]*?)?       # optionally multiple formats in a row
     ;\s*q=([\d.]+)     # optional "quality" preference (eg q=0.5)
    )?
  }x

  vendor_prefix_pattern = /vnd\.[^+]+\+/

  accept.scan(accept_into_mime_and_quality)
    .sort_by { |_, quality_preference| -quality_preference.to_f }
    .flat_map { |mime, _| [mime, mime.sub(vendor_prefix_pattern, '')] }
end
negotiate_content_type() click to toggle source
# File lib/grape/middleware/formatter.rb, line 121
def negotiate_content_type
  fmt = format_from_extension || format_from_params || options[:format] || format_from_header || options[:default_format]
  if content_type_for(fmt)
    env[Grape::Env::API_FORMAT] = fmt
  else
    throw :error, status: 406, message: "The requested format '#{fmt}' is not supported."
  end
end
read_body_input() click to toggle source

store read input in env

# File lib/grape/middleware/formatter.rb, line 73
def read_body_input
  return unless
    (request.post? || request.put? || request.patch? || request.delete?) &&
    (!request.form_data? || !request.media_type) &&
    (!request.parseable_data?) &&
    (request.content_length.to_i > 0 || request.env[Grape::Http::Headers::HTTP_TRANSFER_ENCODING] == CHUNKED)

  return unless (input = env[Grape::Env::RACK_INPUT])

  input.rewind
  body = env[Grape::Env::API_REQUEST_INPUT] = input.read
  begin
    read_rack_input(body) if body && body.length > 0
  ensure
    input.rewind
  end
end
read_rack_input(body) click to toggle source

store parsed input in env

# File lib/grape/middleware/formatter.rb, line 92
def read_rack_input(body)
  fmt = mime_types[request.media_type] if request.media_type
  fmt ||= options[:default_format]
  if content_type_for(fmt)
    parser = Grape::Parser.parser_for fmt, options
    if parser
      begin
        body = (env[Grape::Env::API_REQUEST_BODY] = parser.call(body, env))
        if body.is_a?(Hash)
          if env[Grape::Env::RACK_REQUEST_FORM_HASH]
            env[Grape::Env::RACK_REQUEST_FORM_HASH] = env[Grape::Env::RACK_REQUEST_FORM_HASH].merge(body)
          else
            env[Grape::Env::RACK_REQUEST_FORM_HASH] = body
          end
          env[Grape::Env::RACK_REQUEST_FORM_INPUT] = env[Grape::Env::RACK_INPUT]
        end
      rescue Grape::Exceptions::Base => e
        raise e
      rescue StandardError => e
        throw :error, status: 400, message: e.message
      end
    else
      env[Grape::Env::API_REQUEST_BODY] = body
    end
  else
    throw :error, status: 406, message: "The requested content-type '#{request.media_type}' is not supported."
  end
end
request() click to toggle source
# File lib/grape/middleware/formatter.rb, line 68
def request
  @request ||= Rack::Request.new(env)
end