Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 25 additions & 37 deletions lib/utopia/controller/responder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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!}.
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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
Expand All @@ -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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: Ooh, opportunity to assign from media_types once, from the if-as-expression.


handler, media_range = @handlers.for(media_types)

Expand All @@ -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.
Expand All @@ -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
Expand Down
6 changes: 6 additions & 0 deletions test/utopia/controller/respond.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions utopia.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading