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,66 +79,97 @@ 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+ # Incrementally constructs an immutable responder.
83+ class Builder
84+ # Initialize an empty responder builder.
85+ def initialize
86+ @handlers = Protocol ::Media ::Map ::Builder . new
87+ @passthrough = nil
88+ end
89+
90+ # Add a serializer for the specified content type.
91+ # @parameter content_type [String | Protocol::Media::Type] The produced media type.
92+ # @yields The response handler body.
93+ # @returns [self] This builder.
94+ def handle ( content_type , &block )
95+ @handlers [ content_type ] = Handler . new ( content_type , block ) . freeze
96+ return self
97+ end
98+
99+ # Register the default JSON handler.
100+ # @returns [self] This builder.
101+ def with_json
102+ @handlers [ Handlers ::JSON ::APPLICATION_JSON ] = Handlers ::JSON
103+ return self
104+ end
105+
106+ # Register the wildcard passthrough handler.
107+ # @returns [self] This builder.
108+ def with_passthrough
109+ @passthrough = Handlers ::Passthrough
110+ return self
111+ end
112+
113+ # Add a serializer for the specified content type.
114+ # @parameter content_type [String | Protocol::Media::Type] The produced media type.
115+ # @yields The response handler body.
116+ # @returns [self] This builder.
117+ def with ( content_type , &block )
118+ return handle ( content_type , &block )
119+ end
120+
121+ # Compile the declarations into an immutable responder.
122+ # @returns [Responder] The responder.
123+ def build
124+ return Responder . new ( @handlers . build , @passthrough ) . freeze
125+ end
101126 end
102127
103- attr :handlers
128+ # Construct an immutable responder using a builder.
129+ # @yields {|builder| ...} The mutable builder.
130+ # @returns [Responder] The responder.
131+ def self . build
132+ builder = Builder . new
133+ yield builder
134+ return builder . build
135+ end
104136
105- # Freeze this object and its internal state .
106- # @returns [self] This object .
107- def freeze
108- @ handlers. freeze
109-
110- super
137+ # Initialize a responder with an immutable handler map .
138+ # @parameter handlers [Protocol::Media::Map] The response handlers .
139+ # @parameter passthrough [Object | Nil] The fallback response handler.
140+ def initialize ( handlers , passthrough = nil )
141+ @handlers = handlers
142+ @passthrough = passthrough
111143 end
112144
145+ attr :handlers
146+
113147 # Negotiate the request's accepted media types and invoke the best handler.
114148 # @parameter context [Object] The controller context.
115149 # @parameter request [Utopia::Request] The request.
116150 # @parameter arguments [Array] The arguments.
117151 # @parameter options [Hash] The options.
118152 # @returns [Array(Object, Object) | Nil] The selected content type and body, or `nil` if none matches.
119153 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- )
154+ if accept = request . headers [ "accept" ]
155+ media_types = accept . media_ranges . sort
156+ else
157+ media_types = [ Handlers ::Passthrough ::WILDCARD ]
158+ end
124159
125- handler , media_range = @handlers . for ( media_types )
160+ if match = @handlers . for ( media_types )
161+ handler , media_range = match
162+ elsif @passthrough
163+ handler = @passthrough
164+ media_range = media_types . first
165+ end
126166
127167 if handler
128168 return handler . content_type , handler . call ( context , request , media_range , *arguments , **options )
129169 end
130170
131171 return nil
132172 end
133-
134- # Add a serializer for the specified content type.
135- def handle ( content_type , &block )
136- @handlers << Handler . new ( content_type , block )
137- end
138-
139- # Register the default JSON handler.
140- # @returns [HTTP::Accept::MediaTypes::Map] The updated handler map.
141- def with_json
142- @handlers << Handlers ::JSON
143- end
144-
145- # Register the wildcard passthrough handler.
146- # @returns [HTTP::Accept::MediaTypes::Map] The updated handler map.
147- def with_passthrough
148- @handlers << Handlers ::Passthrough
149- end
150-
151- # Invoke the responder with the given object.
152- # @parameter content_type [String] The content type.
153- # @yields The response handler body.
154- # @returns [HTTP::Accept::MediaTypes::Map] The updated handler map.
155- def with ( content_type , &block )
156- handle ( content_type , &block )
157- end
158173 end
159174 end
160175end
0 commit comments