Skip to content

Commit ae62280

Browse files
Use protocol media for response negotiation.
1 parent c858f56 commit ae62280

3 files changed

Lines changed: 32 additions & 37 deletions

File tree

lib/utopia/controller/responder.rb

Lines changed: 25 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,23 @@
55

66
require_relative "middleware"
77

8+
require "protocol/http/header/accept"
9+
require "protocol/media/map"
10+
require "protocol/media/type"
11+
require "protocol/media/range"
12+
813
module Utopia
914
module Controller
1015
# @namespace
1116
module Handlers
1217
# Serializes controller values as JSON responses.
1318
module JSON
14-
APPLICATION_JSON = HTTP::Accept::ContentType.new("application", "json").freeze
15-
16-
# Delegate content-type splitting to the JSON media type.
17-
# @parameter arguments [Array] The arguments.
18-
# @returns [Array] The resulting values.
19-
def self.split(*arguments)
20-
APPLICATION_JSON.split(*arguments)
21-
end
19+
APPLICATION_JSON = Protocol::Media::Type.new("application", "json").freeze
2220

2321
# Serialize an object as a successful JSON response.
2422
# @parameter context [Object] The context.
2523
# @parameter request [Utopia::Request] The request.
26-
# @parameter media_range [HTTP::Accept::MediaTypes::MediaRange] The negotiated media range.
24+
# @parameter media_range [Protocol::HTTP::Header::Accept::MediaRange] The negotiated media range.
2725
# @parameter object [Object] The object.
2826
# @parameter options [Hash] The options.
2927
# @returns [Object] The result of {Controller::Base#succeed!}.
@@ -38,19 +36,12 @@ def self.call(context, request, media_range, object, **options)
3836

3937
# Passes response values through without transformation.
4038
module Passthrough
41-
WILDCARD = HTTP::Accept::MediaTypes::MediaRange.new("*", "*").freeze
42-
43-
# Delegate content-type splitting to the wildcard media range.
44-
# @parameter arguments [Array] The arguments.
45-
# @returns [Array] The resulting values.
46-
def self.split(*arguments)
47-
WILDCARD.split(*arguments)
48-
end
39+
WILDCARD = Protocol::Media::Range.new("*", "*").freeze
4940

5041
# Accept an object without producing a response.
5142
# @parameter context [Object] The context.
5243
# @parameter request [Utopia::Request] The request.
53-
# @parameter media_range [HTTP::Accept::MediaTypes::MediaRange] The negotiated media range.
44+
# @parameter media_range [Protocol::HTTP::Header::Accept::MediaRange] The negotiated media range.
5445
# @parameter object [Object] The object.
5546
# @parameter options [Hash] The options.
5647
# @returns [Nil] No response is produced.
@@ -64,17 +55,10 @@ def self.call(context, request, media_range, object, **options)
6455
class Responder
6556
# A content-type handler and its response block.
6657
Handler = Struct.new(:content_type, :block) do
67-
# Delegate content-type splitting to this handler's content type.
68-
# @parameter arguments [Array] The arguments.
69-
# @returns [Array] The resulting values.
70-
def split(*arguments)
71-
self.content_type.split(*arguments)
72-
end
73-
7458
# Invoke this handler's block in the controller context.
7559
# @parameter context [Object] The context.
7660
# @parameter request [Utopia::Request] The request.
77-
# @parameter media_range [HTTP::Accept::MediaTypes::MediaRange] The negotiated media range.
61+
# @parameter media_range [Protocol::HTTP::Header::Accept::MediaRange] The negotiated media range.
7862
# @parameter arguments [Array] The arguments.
7963
# @parameter options [Hash] The options.
8064
# @returns [Object] The handler block's result.
@@ -93,7 +77,7 @@ def with(object, **options)
9377

9478
# Initialize an empty content-type handler map.
9579
def initialize
96-
@handlers = HTTP::Accept::MediaTypes::Map.new
80+
@handlers = Protocol::Media::Map.new
9781
end
9882

9983
attr :handlers
@@ -113,10 +97,11 @@ def freeze
11397
# @parameter options [Hash] The options.
11498
# @returns [Object | Nil] The selected handler's result, or `nil` if none matches.
11599
def call(context, request, *arguments, **options)
116-
# Parse the list of browser preferred content types and return ordered by priority:
117-
media_types = HTTP::Accept::MediaTypes.browser_preferred_media_types(
118-
HTTP::Accept::MediaTypes::HTTP_ACCEPT => Array(request.headers["accept"]).join(",")
119-
)
100+
if accept = request.headers["accept"]
101+
media_types = accept.media_ranges.sort
102+
else
103+
media_types = [Handlers::Passthrough::WILDCARD]
104+
end
120105

121106
handler, media_range = @handlers.for(media_types)
122107

@@ -127,7 +112,8 @@ def call(context, request, *arguments, **options)
127112

128113
# Add a converter for the specified content type. Call the block with the response content if the request accepts the specified content_type.
129114
def handle(content_type, &block)
130-
@handlers << Handler.new(content_type, block)
115+
@handlers[content_type] = Handler.new(content_type, block)
116+
return @handlers
131117
end
132118

133119
# Bind this responder to a context and request.
@@ -139,21 +125,23 @@ def respond_to(context, request)
139125
end
140126

141127
# Register the default JSON handler.
142-
# @returns [HTTP::Accept::MediaTypes::Map] The updated handler map.
128+
# @returns [Protocol::Media::Map] The updated handler map.
143129
def with_json
144-
@handlers << Handlers::JSON
130+
@handlers[Handlers::JSON::APPLICATION_JSON] = Handlers::JSON
131+
return @handlers
145132
end
146133

147134
# Register the wildcard passthrough handler.
148-
# @returns [HTTP::Accept::MediaTypes::Map] The updated handler map.
135+
# @returns [Protocol::Media::Map] The updated handler map.
149136
def with_passthrough
150-
@handlers << Handlers::Passthrough
137+
@handlers[Handlers::Passthrough::WILDCARD] = Handlers::Passthrough
138+
return @handlers
151139
end
152140

153141
# Invoke the responder with the given object.
154142
# @parameter content_type [String] The content type.
155143
# @yields The response handler body.
156-
# @returns [HTTP::Accept::MediaTypes::Map] The updated handler map.
144+
# @returns [Protocol::Media::Map] The updated handler map.
157145
def with(content_type, &block)
158146
handle(content_type, &block)
159147
end

test/utopia/controller/respond.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ def mock_request(path, headers = {})
7373
expect(response.read).to be == '{"user_id":10}'
7474
end
7575

76+
it "can register a passthrough handler" do
77+
responder = Utopia::Controller::Responder.new
78+
79+
expect(responder.with_passthrough).to be == responder.handlers
80+
expect(responder.handlers["*/*"]).to be == Utopia::Controller::Handlers::Passthrough
81+
end
7682
end
7783

7884
describe Utopia::Controller do

utopia.gemspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ Gem::Specification.new do |spec|
3535
spec.add_dependency "msgpack"
3636
spec.add_dependency "net-smtp"
3737
spec.add_dependency "protocol-http", "~> 0.58"
38+
spec.add_dependency "protocol-media", "~> 0.1"
3839
spec.add_dependency "protocol-url", "~> 0.4"
3940
spec.add_dependency "samovar", "~> 2.1"
4041
spec.add_dependency "traces", "~> 0.10"

0 commit comments

Comments
 (0)