lib/roda/plugins/json_parser.rb
# frozen-string-literal: true require 'json' class Roda module RodaPlugins # The json_parser plugin parses request bodies in json format # if the request's content type specifies json. This is mostly # designed for use with JSON API sites. # # This only parses the request body as JSON if the Content-Type # header for the request includes "json". module JsonParser OPTS = {}.freeze JSON_PARAMS_KEY = "roda.json_params".freeze INPUT_KEY = "rack.input".freeze FORM_HASH_KEY = "rack.request.form_hash".freeze FORM_INPUT_KEY = "rack.request.form_input".freeze DEFAULT_ERROR_HANDLER = proc{|r| r.halt [400, {}, []]} DEFAULT_PARSER = JSON.method(:parse) # Handle options for the json_parser plugin: # :error_handler :: A proc to call if an exception is raised when # parsing a JSON request body. The proc is called # with the request object, and should probably call # halt on the request or raise an exception. # :parser :: The parser to use for parsing incoming json. Should be # an object that responds to +call(str)+ and returns the # parsed data. The default is to call JSON.parse. # :include_request :: If true, the parser will be called with the request # object as the second argument, so the parser needs # to respond to +call(str, request)+. def self.configure(app, opts=OPTS) app.opts[:json_parser_error_handler] = opts[:error_handler] || app.opts[:json_parser_error_handler] || DEFAULT_ERROR_HANDLER app.opts[:json_parser_parser] = opts[:parser] || app.opts[:json_parser_parser] || DEFAULT_PARSER app.opts[:json_parser_include_request] = opts[:include_request] || app.opts[:json_parser_include_request] end module RequestMethods # If the Content-Type header in the request includes "json", # parse the request body as JSON. Ignore an empty request body. def POST env = @env if post_params = (env[JSON_PARAMS_KEY] || env[FORM_HASH_KEY]) post_params elsif (input = env[INPUT_KEY]) && content_type =~ /json/ str = input.read input.rewind return super if str.empty? begin json_params = env[JSON_PARAMS_KEY] = parse_json(str) rescue roda_class.opts[:json_parser_error_handler].call(self) end env[FORM_INPUT_KEY] = input json_params else super end end private def parse_json(str) args = [str] args << self if roda_class.opts[:json_parser_include_request] roda_class.opts[:json_parser_parser].call(*args) end end end register_plugin(:json_parser, JsonParser) end end