如何以CSV格式提供Grape返回错误消息?

时间:2014-06-26 02:21:43

标签: ruby-on-rails grape grape-api

我有一个Rails应用程序,我使用Grape gem实现了api。现在,我创建了一个自定义错误格式化程序(CSVFormatter)以CSV格式返回错误响应。

而且,我在我的应用程序的v2.rb文件中也有这个:

error_formatter :csv, Api::Base::Errors::CSVFormatter

当我点击这样的网址时:

http://example.com/api/v2/datasets/CODE/data.csv?&trim_start=06/01/99&trim_end=2014-05/28&sort_order=desc

它在控制台中显示错误,这很好,这意味着我的自定义错误格式化程序工作正常:

Error 
trim_start is invalid 
trim_end is invalid

但是,我只需要在csv文件中下载此错误消息。看了Grape的文档后,我发现了一种设置Content-type的方法,我尝试了这个:

      rack = Rack::Response.new(as_csv , 422, { "Content-type" => "text/csv" }).finish
      rack[2].body[0]

但是,这不符合我的预期。

修改

根据Simon的回答,看起来没有干净的方法使用葡萄而没有强行覆盖状态代码。但是,有人可能不希望这样做,因为它可能会导致应用程序中的其他问题,例如,如果某些其他程序试图从api读取数据并得到错误的响应,或者甚至不知道原因。

1 个答案:

答案 0 :(得分:1)

您正在寻找the Content-Disposition header。将其包含在您的回复中:

Content-Disposition: attachment; filename=error.csv

Web浏览器会将响应正文视为要下载的文件(在此示例中为" error.csv")。

然而,修改代码来做到这一点很复杂:

  • the Grape source code来看,它无法在错误格式化程序中设置响应标头,因此您需要添加格式化的自定义异常处理程序响应正文并为您计划支持的每种输出格式设置适当的响应标题。

  • 根据我的实验,如果HTTP状态代码指示错误(例如400或500范围内的任何内容),浏览器将忽略Content-Disposition标头,因此当状态代码还需要被覆盖时用户请求CSV文件。

尝试将此添加到您的API类:

# Handle all exceptions with an error response appropriate to the requested
# output format
rescue_from :all do |e|
  # Edit this hash to override the HTTP response status for specific output
  # formats
  FORMAT_SPECIFIC_STATUS = {
    :csv => 200
  }

  # Edit this hash to add custom headers specific to each output format
  FORMAT_SPECIFIC_HEADERS = {
    :csv => {
      'Content-Disposition' => 'attachment; filename=error.csv'
    }
  }

  # Get the output format requested by the user
  format = env['api.format']

  # Set the HTTP status appropriately for the requested output format and
  # the error type
  status = FORMAT_SPECIFIC_STATUS[format] ||
             (e.respond_to? :status) && e.status ||
             500

  # Set the HTTP headers appropriately for the requested format
  headers = {
    'Content-Type' => options[:content_types][format] || 'text/plain'
  }.merge(FORMAT_SPECIFIC_HEADERS[format] || { })

  # Format the message body using the appropriate error formatter
  error_formatter =
    options[:error_formatters][format] || options[:default_error_formatter]
  body = error_formatter.call(e.message, nil, options, env)

  # Return the error response to the client in the correct format
  # with the correct HTTP headers for that format
  Rack::Response.new(body, status, headers).finish
end

现在,如果您将API类配置为处理两种不同的格式(为了简单起见,我在这里选择了CSV和纯文本),如下所示:

module Errors
  module CSVErrorFormatter
    def self.call(message, backtrace, options, env)
      as_csv = "CSV formatter:" + "\n"
      message.split(",").each do |msg|
        as_csv += msg + "\n"
      end

      # Note this method simply returns the response body
      as_csv
    end
  end

  module TextErrorFormatter
    def self.call(message, backtrace, options, env)
      as_txt = "Text formatter:" + "\n"
      message.split(",").each do |msg|
        as_txt += msg + "\n"
      end

      as_txt
    end
  end
end

content_type :csv, 'text/csv'
content_type :txt, 'text/plain'

error_formatter :csv, Api::Base::Errors::CSVErrorFormatter
error_formatter :txt, Api::Base::Errors::TextErrorFormatter

您应该会发现您的API始终会返回适合所请求格式的错误响应,并且只有在请求CSV格式时才会触发浏览器下载响应。当然,通过明确声明内容类型和错误格式化,可以扩展为支持尽可能多的格式。

请注意,在这种情况下,此代码不会自动执行正确的操作,并且在使用{{1}直接调用错误响应时}}。在这种情况下,您必须提供正确的正文和标题作为通话本身的一部分。我将上述代码的相关部分提取为可重用的方法,作为读者的练习。