diff --git a/lib/utopia/controller/responder.rb b/lib/utopia/controller/responder.rb index 2d0799b0..0427fb98 100644 --- a/lib/utopia/controller/responder.rb +++ b/lib/utopia/controller/responder.rb @@ -5,25 +5,23 @@ require_relative "middleware" +require "protocol/http/header/accept" +require "protocol/media/map" +require "protocol/media/type" +require "protocol/media/range" + module Utopia module Controller # @namespace module Handlers # Serializes controller values as JSON responses. module JSON - APPLICATION_JSON = HTTP::Accept::ContentType.new("application", "json").freeze - - # Delegate content-type splitting to the JSON media type. - # @parameter arguments [Array] The arguments. - # @returns [Array] The resulting values. - def self.split(*arguments) - APPLICATION_JSON.split(*arguments) - end + APPLICATION_JSON = Protocol::Media::Type.new("application", "json").freeze # Serialize an object as a successful JSON response. # @parameter context [Object] The context. # @parameter request [Utopia::Request] The request. - # @parameter media_range [HTTP::Accept::MediaTypes::MediaRange] The negotiated media range. + # @parameter media_range [Protocol::HTTP::Header::Accept::MediaRange] The negotiated media range. # @parameter object [Object] The object. # @parameter options [Hash] The options. # @returns [Object] The result of {Controller::Base#succeed!}. @@ -38,19 +36,12 @@ def self.call(context, request, media_range, object, **options) # Passes response values through without transformation. module Passthrough - WILDCARD = HTTP::Accept::MediaTypes::MediaRange.new("*", "*").freeze - - # Delegate content-type splitting to the wildcard media range. - # @parameter arguments [Array] The arguments. - # @returns [Array] The resulting values. - def self.split(*arguments) - WILDCARD.split(*arguments) - end + WILDCARD = Protocol::Media::Range.new("*", "*").freeze # Accept an object without producing a response. # @parameter context [Object] The context. # @parameter request [Utopia::Request] The request. - # @parameter media_range [HTTP::Accept::MediaTypes::MediaRange] The negotiated media range. + # @parameter media_range [Protocol::HTTP::Header::Accept::MediaRange] The negotiated media range. # @parameter object [Object] The object. # @parameter options [Hash] The options. # @returns [Nil] No response is produced. @@ -64,17 +55,10 @@ def self.call(context, request, media_range, object, **options) class Responder # A content-type handler and its response block. Handler = Struct.new(:content_type, :block) do - # Delegate content-type splitting to this handler's content type. - # @parameter arguments [Array] The arguments. - # @returns [Array] The resulting values. - def split(*arguments) - self.content_type.split(*arguments) - end - # Invoke this handler's block in the controller context. # @parameter context [Object] The context. # @parameter request [Utopia::Request] The request. - # @parameter media_range [HTTP::Accept::MediaTypes::MediaRange] The negotiated media range. + # @parameter media_range [Protocol::HTTP::Header::Accept::MediaRange] The negotiated media range. # @parameter arguments [Array] The arguments. # @parameter options [Hash] The options. # @returns [Object] The handler block's result. @@ -93,7 +77,7 @@ def with(object, **options) # Initialize an empty content-type handler map. def initialize - @handlers = HTTP::Accept::MediaTypes::Map.new + @handlers = Protocol::Media::Map.new end attr :handlers @@ -113,10 +97,11 @@ def freeze # @parameter options [Hash] The options. # @returns [Object | Nil] The selected handler's result, or `nil` if none matches. def call(context, request, *arguments, **options) - # Parse the list of browser preferred content types and return ordered by priority: - media_types = HTTP::Accept::MediaTypes.browser_preferred_media_types( - HTTP::Accept::MediaTypes::HTTP_ACCEPT => Array(request.headers["accept"]).join(",") - ) + if accept = request.headers["accept"] + media_types = accept.media_ranges.sort + else + media_types = [Handlers::Passthrough::WILDCARD] + end handler, media_range = @handlers.for(media_types) @@ -127,7 +112,8 @@ def call(context, request, *arguments, **options) # Add a converter for the specified content type. Call the block with the response content if the request accepts the specified content_type. def handle(content_type, &block) - @handlers << Handler.new(content_type, block) + @handlers[content_type] = Handler.new(content_type, block) + return @handlers end # Bind this responder to a context and request. @@ -139,21 +125,23 @@ def respond_to(context, request) end # Register the default JSON handler. - # @returns [HTTP::Accept::MediaTypes::Map] The updated handler map. + # @returns [Protocol::Media::Map] The updated handler map. def with_json - @handlers << Handlers::JSON + @handlers[Handlers::JSON::APPLICATION_JSON] = Handlers::JSON + return @handlers end # Register the wildcard passthrough handler. - # @returns [HTTP::Accept::MediaTypes::Map] The updated handler map. + # @returns [Protocol::Media::Map] The updated handler map. def with_passthrough - @handlers << Handlers::Passthrough + @handlers[Handlers::Passthrough::WILDCARD] = Handlers::Passthrough + return @handlers end # Invoke the responder with the given object. # @parameter content_type [String] The content type. # @yields The response handler body. - # @returns [HTTP::Accept::MediaTypes::Map] The updated handler map. + # @returns [Protocol::Media::Map] The updated handler map. def with(content_type, &block) handle(content_type, &block) end diff --git a/test/utopia/controller/respond.rb b/test/utopia/controller/respond.rb index b2a270fc..c4a1cd88 100644 --- a/test/utopia/controller/respond.rb +++ b/test/utopia/controller/respond.rb @@ -72,6 +72,12 @@ def mock_request(path, headers = {}) expect(response.read).to be == '{"user_id":10}' end + it "can register a passthrough handler" do + responder = Utopia::Controller::Responder.new + + expect(responder.with_passthrough).to be == responder.handlers + expect(responder.handlers["*/*"]).to be == Utopia::Controller::Handlers::Passthrough + end end describe Utopia::Controller do diff --git a/utopia.gemspec b/utopia.gemspec index 2694a771..d7ee880e 100644 --- a/utopia.gemspec +++ b/utopia.gemspec @@ -35,6 +35,7 @@ Gem::Specification.new do |spec| spec.add_dependency "msgpack" spec.add_dependency "net-smtp" spec.add_dependency "protocol-http", "~> 0.67" + spec.add_dependency "protocol-media", "~> 0.1" spec.add_dependency "protocol-url", "~> 0.4" spec.add_dependency "samovar", "~> 2.1" spec.add_dependency "traces", "~> 0.10"