55
66require_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+
813module 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 JSON.
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 [String] The serialized JSON body.
@@ -36,27 +34,20 @@ def self.call(context, request, media_range, object, **options)
3634 end
3735
3836 # The media type produced by this handler.
39- # @returns [HTTP::Accept::ContentType ] The JSON media type.
37+ # @returns [Protocol::Media::Type ] The JSON media type.
4038 def self . content_type
4139 APPLICATION_JSON
4240 end
4341 end
4442
4543 # Passes response values through without transformation.
4644 module Passthrough
47- WILDCARD = HTTP ::Accept ::MediaTypes ::MediaRange . new ( "*" , "*" ) . freeze
48-
49- # Delegate content-type splitting to the wildcard media range.
50- # @parameter arguments [Array] The arguments.
51- # @returns [Array] The resulting values.
52- def self . split ( *arguments )
53- WILDCARD . split ( *arguments )
54- end
45+ WILDCARD = Protocol ::Media ::Range . new ( "*" , "*" ) . freeze
5546
5647 # Pass an object through without transformation.
5748 # @parameter context [Object] The context.
5849 # @parameter request [Utopia::Request] The request.
59- # @parameter media_range [HTTP::Accept::MediaTypes ::MediaRange] The negotiated media range.
50+ # @parameter media_range [Protocol:: HTTP::Header::Accept ::MediaRange] The negotiated media range.
6051 # @parameter object [Object] The object.
6152 # @parameter options [Hash] The options.
6253 # @returns [Object] The original body.
@@ -76,17 +67,10 @@ def self.content_type
7667 class Responder
7768 # A content-type handler and its response block.
7869 Handler = Struct . new ( :content_type , :block ) do
79- # Delegate content-type splitting to this handler's content type.
80- # @parameter arguments [Array] The arguments.
81- # @returns [Array] The resulting values.
82- def split ( *arguments )
83- self . content_type . split ( *arguments )
84- end
85-
8670 # Invoke this handler's block in the controller context.
8771 # @parameter context [Object] The context.
8872 # @parameter request [Utopia::Request] The request.
89- # @parameter media_range [HTTP::Accept::MediaTypes ::MediaRange] The negotiated media range.
73+ # @parameter media_range [Protocol:: HTTP::Header::Accept ::MediaRange] The negotiated media range.
9074 # @parameter arguments [Array] The arguments.
9175 # @parameter options [Hash] The options.
9276 # @returns [Object] The handler block's result.
@@ -95,65 +79,85 @@ def call(context, request, media_range, *arguments, **options)
9579 end
9680 end
9781
98- # Initialize an empty content-type handler map.
99- def initialize
100- @handlers = HTTP ::Accept ::MediaTypes ::Map . new
82+ # Initialize a responder with a handler map.
83+ # @parameter handlers [Protocol::Media::Map] The response handlers.
84+ # @parameter passthrough [Object | Nil] The fallback response handler.
85+ def initialize ( handlers = Protocol ::Media ::Map . new , passthrough = nil )
86+ @handlers = handlers
87+ @passthrough = passthrough
10188 end
10289
10390 attr :handlers
10491
105- # Freeze this object and its internal state .
106- # @returns [self] This object .
92+ # Freeze this responder and compile its handler map .
93+ # @returns [self] This responder .
10794 def freeze
108- @handlers . freeze
109-
110- super
111- end
112-
113- # Negotiate the request's accepted media types and invoke the best handler.
114- # @parameter context [Object] The controller context.
115- # @parameter request [Utopia::Request] The request.
116- # @parameter arguments [Array] The arguments.
117- # @parameter options [Hash] The options.
118- # @returns [Array(Object, Object) | Nil] The selected content type and body, or `nil` if none matches.
119- def call ( context , request , *arguments , **options )
120- # Parse the list of browser preferred content types and return ordered by priority:
121- media_types = HTTP ::Accept ::MediaTypes . browser_preferred_media_types (
122- HTTP ::Accept ::MediaTypes ::HTTP_ACCEPT => Array ( request . headers [ "accept" ] ) . join ( "," )
123- )
95+ return self if frozen?
12496
125- handler , media_range = @handlers . for ( media_types )
126-
127- if handler
128- return handler . content_type , handler . call ( context , request , media_range , *arguments , **options )
129- end
97+ @handlers . freeze
13098
131- return nil
99+ return super
132100 end
133101
134102 # Add a serializer for the specified content type.
103+ # @parameter content_type [String | Protocol::Media::Type] The produced media type.
104+ # @yields The response handler body.
105+ # @returns [self] This responder.
135106 def handle ( content_type , &block )
136- @handlers << Handler . new ( content_type , block )
107+ @handlers [ content_type ] = Handler . new ( content_type , block ) . freeze
108+ return self
137109 end
138110
139111 # Register the default JSON handler.
140- # @returns [HTTP::Accept::MediaTypes::Map] The updated handler map .
112+ # @returns [self] This responder .
141113 def with_json
142- @handlers << Handlers ::JSON
114+ @handlers [ Handlers ::JSON ::APPLICATION_JSON ] = Handlers ::JSON
115+ return self
143116 end
144117
145118 # Register the wildcard passthrough handler.
146- # @returns [HTTP::Accept::MediaTypes::Map] The updated handler map .
119+ # @returns [self] This responder .
147120 def with_passthrough
148- @handlers << Handlers ::Passthrough
121+ @passthrough = Handlers ::Passthrough
122+ return self
149123 end
150124
151- # Invoke the responder with the given object .
152- # @parameter content_type [String] The content type.
125+ # Add a serializer for the specified content type .
126+ # @parameter content_type [String | Protocol::Media::Type ] The produced media type.
153127 # @yields The response handler body.
154- # @returns [HTTP::Accept::MediaTypes::Map] The updated handler map .
128+ # @returns [self] This responder .
155129 def with ( content_type , &block )
156- handle ( content_type , &block )
130+ return handle ( content_type , &block )
131+ end
132+
133+ # Negotiate the request's accepted media types and invoke the best handler.
134+ # @parameter context [Object] The controller context.
135+ # @parameter request [Utopia::Request] The request.
136+ # @parameter arguments [Array] The arguments.
137+ # @parameter options [Hash] The options.
138+ # @returns [Array(Object, Object) | Nil] The selected content type and body, or `nil` if none matches.
139+ def call ( context , request , *arguments , **options )
140+ accept = request . headers [ "accept" ]
141+
142+ # An absent or empty Accept header accepts any media type:
143+ if accept . nil? || accept . empty?
144+ media_ranges = [ Handlers ::Passthrough ::WILDCARD ]
145+ else
146+ media_ranges = accept . preferred_media_ranges
147+ end
148+
149+ if match = @handlers . for ( media_ranges )
150+ handler , media_range = match
151+ elsif @passthrough
152+ handler = @passthrough
153+ media_range = media_ranges . first
154+ end
155+
156+ if handler
157+ return handler . content_type , handler . call ( context , request , media_range , *arguments , **options )
158+ end
159+
160+ return nil
157161 end
158162 end
159163 end
0 commit comments