001/*
002 * Copyright 2022-2026 Revetware LLC.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package com.soklet;
018
019import org.jspecify.annotations.NonNull;
020import org.jspecify.annotations.Nullable;
021
022import javax.annotation.concurrent.NotThreadSafe;
023import java.nio.charset.Charset;
024import java.nio.charset.StandardCharsets;
025import java.util.Set;
026
027import static java.util.Objects.requireNonNull;
028
029/**
030 * Prepares responses for each request scenario Soklet supports (<em>Resource Method</em>, exception, CORS preflight, etc.)
031 * <p>
032 * The {@link MarshaledResponse} value returned from these methods is what is ultimately sent back to
033 * clients as bytes over the wire.
034 * <p>
035 * A standard threadsafe implementation builder can be acquired via {@link #builder()} or {@link #withCharset(Charset)}.
036 * This builder allows you to specify, for example, how to turn a <em>Resource Method</em> response object into a wire format (e.g. JSON) and is generally what you want.
037 * <p>
038 * A standard threadsafe implementation can be acquired via the {@link #defaultInstance()} factory method.
039 * This is generally not needed unless your implementation requires dynamic "fall back to default" behavior that is not otherwise accessible.
040 * <p>
041 * Example implementation using {@link #builder()}:
042 * <pre>{@code // Let's use Gson to write response body data
043 * // See https://github.com/google/gson
044 * final Gson GSON = new Gson();
045 *
046 * // The request was matched to a Resource Method and executed non-exceptionally
047 * ResourceMethodHandler resourceMethodHandler = (
048 *   @NonNull Request request,
049 *   @NonNull Response response,
050 *   @NonNull ResourceMethod resourceMethod
051 * ) -> {
052 *   // Turn response body into JSON bytes with Gson
053 *   Object bodyObject = response.getBody().orElse(null);
054 *   byte[] body = bodyObject == null
055 *     ? null
056 *     : GSON.toJson(bodyObject).getBytes(StandardCharsets.UTF_8);
057 *
058 *   // To be a good citizen, set the Content-Type header
059 *   Map<String, Set<String>> headers = new HashMap<>(response.getHeaders());
060 *   headers.put("Content-Type", Set.of("application/json;charset=UTF-8"));
061 *
062 *   // Tell Soklet: "OK - here is the final response data to send"
063 *   return MarshaledResponse.withResponse(response)
064 *     .headers(headers)
065 *     .body(body)
066 *     .build();
067 * };
068 *
069 * // Function to create responses for exceptions that bubble out
070 * ThrowableHandler throwableHandler = (
071 *   @NonNull Request request,
072 *   @NonNull Throwable throwable,
073 *   @Nullable ResourceMethod resourceMethod
074 * ) -> {
075 *   // Keep track of what to write to the response
076 *   String message;
077 *   int statusCode;
078 *
079 *   // Examine the exception that bubbled out and determine what
080 *   // the HTTP status and a user-facing message should be.
081 *   // Note: real systems should localize these messages
082 *   switch (throwable) {
083 *     // Soklet throws this exception, a specific subclass of BadRequestException
084 *     case IllegalQueryParameterException e -> {
085 *       message = String.format("Illegal value '%s' for parameter '%s'",
086 *         e.getQueryParameterValue().orElse("[not provided]"),
087 *         e.getQueryParameterName());
088 *       statusCode = 400;
089 *     }
090 *
091 *     // Generically handle other BadRequestExceptions
092 *     case BadRequestException ignored -> {
093 *       message = "Your request was improperly formatted.";
094 *       statusCode = 400;
095 *     }
096 *
097 *     // Something else?  Fall back to a 500
098 *     default -> {
099 *       message = "An unexpected error occurred.";
100 *       statusCode = 500;
101 *     }
102 *   }
103 *
104 *   // Turn response body into JSON bytes with Gson.
105 *   // Note: real systems should expose richer error constructs
106 *   // than an object with a single message field
107 *   byte[] body = GSON.toJson(Map.of("message", message))
108 *     .getBytes(StandardCharsets.UTF_8);
109 *
110 *   // Specify our headers
111 *   Map<String, Set<String>> headers = new HashMap<>();
112 *   headers.put("Content-Type", Set.of("application/json;charset=UTF-8"));
113 *
114 *   return MarshaledResponse.withStatusCode(statusCode)
115 *     .headers(headers)
116 *     .body(body)
117 *     .build();
118 * };
119 *
120 * // Supply our custom handlers to the standard response marshaler
121 * SokletConfig config = SokletConfig.withHttpServer(
122 *   HttpServer.fromPort(8080)
123 * ).responseMarshaler(ResponseMarshaler.builder()
124 *   .resourceMethodHandler(resourceMethodHandler)
125 *   .throwableHandler(throwableHandler)
126 *   .build()
127 * ).build();}</pre>
128 * <p>
129 * Full documentation is available at <a href="https://www.soklet.com/docs/response-writing">https://www.soklet.com/docs/response-writing</a>.
130 *
131 * @author <a href="https://www.revetkn.com">Mark Allen</a>
132 */
133public interface ResponseMarshaler {
134        /**
135         * Prepares a response for a request that was matched to a <em>Resource Method</em> and returned normally (i.e., without throwing an exception).
136         * <p>
137         * <strong>Note that the returned {@link Response} may represent any HTTP status (e.g., 200, 403, 404), and is not restricted to "successful" outcomes.</strong>
138         * <p>
139         * Detailed documentation is available at <a href="https://www.soklet.com/docs/response-writing#resource-method">https://www.soklet.com/docs/response-writing#resource-method</a>.
140         *
141         * @param request        the HTTP request
142         * @param response       the response provided by the <em>Resource Method</em> that handled the request
143         * @param resourceMethod the <em>Resource Method</em> that handled the request
144         * @return the response to be sent over the wire
145         */
146        @NonNull
147        MarshaledResponse forResourceMethod(@NonNull Request request,
148                                                                                                                                                        @NonNull Response response,
149                                                                                                                                                        @NonNull ResourceMethod resourceMethod);
150
151        /**
152         * Prepares a response for a request that does not have a matching <em>Resource Method</em>, which triggers an <a href="https://httpwg.org/specs/rfc9110.html#status.404">HTTP 404 Not Found</a>.
153         * <p>
154         * Detailed documentation is available at <a href="https://www.soklet.com/docs/response-writing#404-not-found">https://www.soklet.com/docs/response-writing#404-not-found</a>.
155         *
156         * @param request the HTTP request
157         * @return the response to be sent over the wire
158         */
159        @NonNull
160        MarshaledResponse forNotFound(@NonNull Request request);
161
162        /**
163         * Prepares a response for a request that triggers an
164         * <a href="https://httpwg.org/specs/rfc9110.html#status.405">HTTP 405 Method Not Allowed</a>.
165         * <p>
166         * Detailed documentation is available at <a href="https://www.soklet.com/docs/response-writing#405-method-not-allowed">https://www.soklet.com/docs/response-writing#405-method-not-allowed</a>.
167         *
168         * @param request            the HTTP request
169         * @param allowedHttpMethods appropriate HTTP methods to write to the {@code Allow} response header
170         * @return the response to be sent over the wire
171         */
172        @NonNull
173        MarshaledResponse forMethodNotAllowed(@NonNull Request request,
174                                                                                                                                                                @NonNull Set<@NonNull HttpMethod> allowedHttpMethods);
175
176        /**
177         * Prepares a response for a request that triggers an <a href="https://httpwg.org/specs/rfc9110.html#status.413">HTTP 413 Content Too Large</a>.
178         * <p>
179         * This method is only invoked when the transport can parse enough request data to construct a {@link Request}.
180         * If the size limit is exceeded before a request target can be identified, the transport may close the connection instead.
181         * <p>
182         * Detailed documentation is available at <a href="https://www.soklet.com/docs/response-writing#413-content-too-large">https://www.soklet.com/docs/response-writing#413-content-too-large</a>.
183         *
184         * @param request        the HTTP request
185         * @param resourceMethod the <em>Resource Method</em> that would have handled the request, if available
186         * @return the response to be sent over the wire
187         */
188        @NonNull
189        MarshaledResponse forContentTooLarge(@NonNull Request request,
190                                                                                                                                                         @Nullable ResourceMethod resourceMethod);
191
192        /**
193         * Prepares a response for a request that is rejected because the server is overloaded (e.g. connection limit reached), triggering an <a href="https://httpwg.org/specs/rfc9110.html#status.503">HTTP 503 Service Unavailable</a>.
194         * <p>
195         * Detailed documentation is available at <a href="https://www.soklet.com/docs/response-writing#503-service-unavailable">https://www.soklet.com/docs/response-writing#503-service-unavailable</a>.
196         *
197         * @param request        the HTTP request
198         * @param resourceMethod the <em>Resource Method</em> that would have handled the request, if available
199         * @return the response to be sent over the wire
200         */
201        @NonNull
202        MarshaledResponse forServiceUnavailable(@NonNull Request request,
203                                                                                                                                                                        @Nullable ResourceMethod resourceMethod);
204
205        /**
206         * Prepares a response for an HTTP {@code OPTIONS} request.
207         * <p>
208         * Note that CORS preflight responses are handled specially by {@link #forCorsPreflightAllowed(Request, CorsPreflight, CorsPreflightResponse)}
209         * and {@link #forCorsPreflightRejected(Request, CorsPreflight)} - not this method.
210         * <p>
211         * Detailed documentation is available at <a href="https://www.soklet.com/docs/response-writing#http-options">https://www.soklet.com/docs/response-writing#http-options</a>.
212         *
213         * @param request            the HTTP request
214         * @param allowedHttpMethods appropriate HTTP methods to write to the {@code Allow} response header
215         * @return the response to be sent over the wire
216         */
217        @NonNull
218        MarshaledResponse forOptions(@NonNull Request request,
219                                                                                                                         @NonNull Set<@NonNull HttpMethod> allowedHttpMethods);
220
221        /**
222         * Prepares a response for an HTTP {@code OPTIONS *} (colloquially, "{@code OPTIONS} Splat") request.
223         * <p>
224         * This is a special HTTP/1.1 request defined in RFC 7231 §4.3.7 which permits querying of server-wide capabilities, not the capabilities of a particular resource - e.g. a load balancer asking "is the system up?" without an explicit health-check URL.
225         * <p>
226         * Detailed documentation is available at <a href="https://www.soklet.com/docs/response-writing#http-options">https://www.soklet.com/docs/response-writing#http-options</a>.
227         *
228         * @param request the HTTP request
229         * @return the response to be sent over the wire
230         */
231        @NonNull
232        MarshaledResponse forOptionsSplat(@NonNull Request request);
233
234        /**
235         * Prepares a response for scenarios in which an uncaught exception is encountered.
236         * <p>
237         * Detailed documentation is available at <a href="https://www.soklet.com/docs/response-writing#uncaught-exceptions">https://www.soklet.com/docs/response-writing#uncaught-exceptions</a>.
238         *
239         * @param request        the HTTP request
240         * @param throwable      the exception that was thrown
241         * @param resourceMethod the <em>Resource Method</em> that would have handled the request, if available
242         * @return the response to be sent over the wire
243         */
244        @NonNull
245        MarshaledResponse forThrowable(@NonNull Request request,
246                                                                                                                                 @NonNull Throwable throwable,
247                                                                                                                                 @Nullable ResourceMethod resourceMethod);
248
249        /**
250         * Prepares a response for an HTTP {@code HEAD} request.
251         * <p>
252         * Detailed documentation is available at <a href="https://www.soklet.com/docs/response-writing#http-head">https://www.soklet.com/docs/response-writing#http-head</a>.
253         *
254         * @param request                    the HTTP request
255         * @param getMethodMarshaledResponse the marshaled response that would have been sent over the wire for an equivalent {@code GET} request (necessary in order to write the {@code Content-Length} header for a {@code HEAD} response)
256         * @return the response to be sent over the wire
257         */
258        @NonNull
259        MarshaledResponse forHead(@NonNull Request request,
260                                                                                                                @NonNull MarshaledResponse getMethodMarshaledResponse);
261
262        /**
263         * Prepares a response for "CORS preflight allowed" scenario when your {@link CorsAuthorizer} approves a preflight request.
264         * <p>
265         * Detailed documentation is available at <a href="https://www.soklet.com/docs/cors#writing-cors-responses">https://www.soklet.com/docs/cors#writing-cors-responses</a>.
266         *
267         * @param request               the HTTP request
268         * @param corsPreflight         the CORS preflight request data
269         * @param corsPreflightResponse the data that should be included in this CORS preflight response
270         * @return the response to be sent over the wire
271         */
272        @NonNull
273        MarshaledResponse forCorsPreflightAllowed(@NonNull Request request,
274                                                                                                                                                                                @NonNull CorsPreflight corsPreflight,
275                                                                                                                                                                                @NonNull CorsPreflightResponse corsPreflightResponse);
276
277        /**
278         * Prepares a response for "CORS preflight rejected" scenario when your {@link CorsAuthorizer} denies a preflight request.
279         * <p>
280         * Detailed documentation is available at <a href="https://www.soklet.com/docs/cors#writing-cors-responses">https://www.soklet.com/docs/cors#writing-cors-responses</a>.
281         *
282         * @param request       the HTTP request
283         * @param corsPreflight the CORS preflight request data
284         * @return the response to be sent over the wire
285         */
286        @NonNull
287        MarshaledResponse forCorsPreflightRejected(@NonNull Request request,
288                                                                                                                                                                                 @NonNull CorsPreflight corsPreflight);
289
290        /**
291         * Applies "CORS is permitted for this request" data to a response.
292         * <p>
293         * Invoked for any non-preflight CORS request that your {@link CorsAuthorizer} approves.
294         * <p>
295         * This method will normally return a copy of the {@code marshaledResponse} with these headers applied
296         * based on the values of {@code corsResponse}:
297         * <ul>
298         *   <li>{@code Access-Control-Allow-Origin} (required)</li>
299         *   <li>{@code Access-Control-Allow-Credentials} (optional)</li>
300         *   <li>{@code Access-Control-Expose-Headers} (optional)</li>
301         * </ul>
302         *
303         * @param request           the HTTP request
304         * @param cors              the CORS request data
305         * @param corsResponse      CORS response data to write as specified by {@link CorsAuthorizer}
306         * @param marshaledResponse the existing response to which we should apply relevant CORS headers
307         * @return the response to be sent over the wire
308         */
309        @NonNull
310        MarshaledResponse forCorsAllowed(@NonNull Request request,
311                                                                                                                                         @NonNull Cors cors,
312                                                                                                                                         @NonNull CorsResponse corsResponse,
313                                                                                                                                         @NonNull MarshaledResponse marshaledResponse);
314
315        /**
316         * Acquires a threadsafe {@link ResponseMarshaler} with a reasonable "out of the box" configuration that uses UTF-8 to write character data.
317         * <p>
318         * The returned instance is guaranteed to be a JVM-wide singleton.
319         *
320         * @return a UTF-8 {@code ResponseMarshaler} with default settings
321         */
322        @NonNull
323        static ResponseMarshaler defaultInstance() {
324                return DefaultResponseMarshaler.defaultInstance();
325        }
326
327        /**
328         * Acquires a builder for a default {@link ResponseMarshaler} implementation that uses {@link java.nio.charset.StandardCharsets#UTF_8} encoding.
329         *
330         * @return a {@code ResponseMarshaler} builder
331         */
332        @NonNull
333        static Builder builder() {
334                return new Builder(StandardCharsets.UTF_8);
335        }
336
337        /**
338         * Acquires a builder for a default {@link ResponseMarshaler} implementation.
339         *
340         * @param charset the default charset to use when writing response data
341         * @return a {@code ResponseMarshaler} builder
342         */
343        @NonNull
344        static Builder withCharset(@NonNull Charset charset) {
345                requireNonNull(charset);
346                return new Builder(charset);
347        }
348
349        /**
350         * Builder used to construct a standard implementation of {@link ResponseMarshaler}.
351         * <p>
352         * This class is intended for use by a single thread.
353         *
354         * @author <a href="https://www.revetkn.com">Mark Allen</a>
355         */
356        @NotThreadSafe
357        final class Builder {
358                /**
359                 * Function used to support pluggable implementations of {@link ResponseMarshaler#forResourceMethod(Request, Response, ResourceMethod)}.
360                 */
361                @FunctionalInterface
362                public interface ResourceMethodHandler {
363                        /**
364                         * Prepares a response for the scenario in which the request was matched to a <em>Resource Method</em> and executed non-exceptionally.
365                         * <p>
366                         * Detailed documentation is available at <a href="https://www.soklet.com/docs/response-writing#resource-method">https://www.soklet.com/docs/response-writing#resource-method</a>.
367                         *
368                         * @param request        the HTTP request
369                         * @param response       the response provided by the <em>Resource Method</em> that handled the request
370                         * @param resourceMethod the <em>Resource Method</em> that handled the request
371                         * @return the response to be sent over the wire
372                         */
373                        @NonNull
374                        MarshaledResponse handle(@NonNull Request request,
375                                                                                                                         @NonNull Response response,
376                                                                                                                         @NonNull ResourceMethod resourceMethod);
377                }
378
379                /**
380                 * Function used to support pluggable implementations of {@link ResponseMarshaler#forNotFound(Request)}.
381                 */
382                @FunctionalInterface
383                public interface NotFoundHandler {
384                        /**
385                         * Prepares a response for a request that triggers an
386                         * <a href="https://httpwg.org/specs/rfc9110.html#status.404">HTTP 404 Not Found</a>.
387                         * <p>
388                         * Detailed documentation is available at <a href="https://www.soklet.com/docs/response-writing#404-not-found">https://www.soklet.com/docs/response-writing#404-not-found</a>.
389                         *
390                         * @param request the HTTP request
391                         * @return the response to be sent over the wire
392                         */
393                        @NonNull
394                        MarshaledResponse handle(@NonNull Request request);
395                }
396
397                /**
398                 * Function used to support pluggable implementations of {@link ResponseMarshaler#forMethodNotAllowed(Request, Set)}.
399                 */
400                @FunctionalInterface
401                public interface MethodNotAllowedHandler {
402                        /**
403                         * Prepares a response for a request that triggers an
404                         * <a href="https://httpwg.org/specs/rfc9110.html#status.405">HTTP 405 Method Not Allowed</a>.
405                         * <p>
406                         * Detailed documentation is available at <a href="https://www.soklet.com/docs/response-writing#405-method-not-allowed">https://www.soklet.com/docs/response-writing#405-method-not-allowed</a>.
407                         *
408                         * @param request            the HTTP request
409                         * @param allowedHttpMethods appropriate HTTP methods to write to the {@code Allow} response header
410                         * @return the response to be sent over the wire
411                         */
412                        @NonNull
413                        MarshaledResponse handle(@NonNull Request request,
414                                                                                                                         @NonNull Set<@NonNull HttpMethod> allowedHttpMethods);
415                }
416
417                /**
418                 * Function used to support pluggable implementations of {@link ResponseMarshaler#forContentTooLarge(Request, ResourceMethod)}.
419                 */
420                @FunctionalInterface
421                public interface ContentTooLargeHandler {
422                        /**
423                         * Prepares a response for a request that triggers an <a href="https://httpwg.org/specs/rfc9110.html#status.413">HTTP 413 Content Too Large</a>.
424                         * <p>
425                         * Detailed documentation is available at <a href="https://www.soklet.com/docs/response-writing#413-content-too-large">https://www.soklet.com/docs/response-writing#413-content-too-large</a>.
426                         *
427                         * @param request        the HTTP request
428                         * @param resourceMethod the <em>Resource Method</em> that would have handled the request, if available
429                         * @return the response to be sent over the wire
430                         */
431                        @NonNull
432                        MarshaledResponse handle(@NonNull Request request,
433                                                                                                                         @Nullable ResourceMethod resourceMethod);
434                }
435
436                /**
437                 * Function used to support pluggable implementations of {@link ResponseMarshaler#forServiceUnavailable(Request, ResourceMethod)}.
438                 */
439                @FunctionalInterface
440                public interface ServiceUnavailableHandler {
441                        /**
442                         * Prepares a response for a request that triggers an <a href="https://httpwg.org/specs/rfc9110.html#status.503">HTTP 503 Service Unavailable</a>.
443                         * <p>
444                         * Detailed documentation is available at <a href="https://www.soklet.com/docs/response-writing#503-service-unavailable">https://www.soklet.com/docs/response-writing#503-service-unavailable</a>.
445                         *
446                         * @param request        the HTTP request
447                         * @param resourceMethod the <em>Resource Method</em> that would have handled the request, if available
448                         * @return the response to be sent over the wire
449                         */
450                        @NonNull
451                        MarshaledResponse handle(@NonNull Request request,
452                                                                                                                         @Nullable ResourceMethod resourceMethod);
453                }
454
455                /**
456                 * Function used to support pluggable implementations of {@link ResponseMarshaler#forOptions(Request, Set)}.
457                 */
458                @FunctionalInterface
459                public interface OptionsHandler {
460                        /**
461                         * Prepares a response for an HTTP {@code OPTIONS} request.
462                         * <p>
463                         * Note that CORS preflight responses are handled specially by {@link #forCorsPreflightAllowed(Request, CorsPreflight, CorsPreflightResponse)}
464                         * and {@link #forCorsPreflightRejected(Request, CorsPreflight)} - not this method.
465                         * <p>
466                         * Detailed documentation is available at <a href="https://www.soklet.com/docs/response-writing#http-options">https://www.soklet.com/docs/response-writing#http-options</a>.
467                         *
468                         * @param request            the HTTP request
469                         * @param allowedHttpMethods appropriate HTTP methods to write to the {@code Allow} response header
470                         * @return the response to be sent over the wire
471                         */
472                        @NonNull
473                        MarshaledResponse handle(@NonNull Request request,
474                                                                                                                         @NonNull Set<@NonNull HttpMethod> allowedHttpMethods);
475                }
476
477                /**
478                 * Function used to support pluggable implementations of {@link ResponseMarshaler#forOptionsSplat(Request)}.
479                 */
480                @FunctionalInterface
481                public interface OptionsSplatHandler {
482                        /**
483                         * Prepares a response for an HTTP {@code OPTIONS *} (colloquially, "{@code OPTIONS} Splat") request.
484                         * <p>
485                         * This is a special HTTP/1.1 request defined in RFC 7231 §4.3.7 which permits querying of server-wide capabilities, not the capabilities of a particular resource - e.g. a load balancer asking "is the system up?" without an explicit health-check URL.
486                         * <p>
487                         * Detailed documentation is available at <a href="https://www.soklet.com/docs/response-writing#http-options">https://www.soklet.com/docs/response-writing#http-options</a>.
488                         *
489                         * @param request the HTTP request
490                         * @return the response to be sent over the wire
491                         */
492                        @NonNull
493                        MarshaledResponse handle(@NonNull Request request);
494                }
495
496                /**
497                 * Function used to support pluggable implementations of {@link ResponseMarshaler#forThrowable(Request, Throwable, ResourceMethod)}.
498                 */
499                @FunctionalInterface
500                public interface ThrowableHandler {
501                        /**
502                         * Prepares a response for scenarios in which an uncaught exception is encountered.
503                         * <p>
504                         * Detailed documentation is available at <a href="https://www.soklet.com/docs/response-writing#uncaught-exceptions">https://www.soklet.com/docs/response-writing#uncaught-exceptions</a>.
505                         *
506                         * @param request        the HTTP request
507                         * @param throwable      the exception that was thrown
508                         * @param resourceMethod the <em>Resource Method</em> that would have handled the request, if available
509                         * @return the response to be sent over the wire
510                         */
511                        @NonNull
512                        MarshaledResponse handle(@NonNull Request request,
513                                                                                                                         @NonNull Throwable throwable,
514                                                                                                                         @Nullable ResourceMethod resourceMethod);
515                }
516
517                /**
518                 * Function used to support pluggable implementations of {@link ResponseMarshaler#forHead(Request, MarshaledResponse)}.
519                 */
520                @FunctionalInterface
521                public interface HeadHandler {
522                        /**
523                         * Prepares a response for an HTTP {@code HEAD} request.
524                         * <p>
525                         * Detailed documentation is available at <a href="https://www.soklet.com/docs/response-writing#http-head">https://www.soklet.com/docs/response-writing#http-head</a>.
526                         *
527                         * @param request                    the HTTP request
528                         * @param getMethodMarshaledResponse the marshaled response that would have been sent over the wire for an equivalent {@code GET} request (necessary in order to write the {@code Content-Length} header for a {@code HEAD} response)
529                         * @return the response to be sent over the wire
530                         */
531                        @NonNull
532                        MarshaledResponse handle(@NonNull Request request,
533                                                                                                                         @NonNull MarshaledResponse getMethodMarshaledResponse);
534                }
535
536                /**
537                 * Function used to support pluggable implementations of {@link ResponseMarshaler#forCorsPreflightAllowed(Request, CorsPreflight, CorsPreflightResponse)}.
538                 */
539                @FunctionalInterface
540                public interface CorsPreflightAllowedHandler {
541                        /**
542                         * Prepares a response for "CORS preflight allowed" scenario when your {@link CorsAuthorizer} approves a preflight request.
543                         * <p>
544                         * Detailed documentation is available at <a href="https://www.soklet.com/docs/cors#writing-cors-responses">https://www.soklet.com/docs/cors#writing-cors-responses</a>.
545                         *
546                         * @param request               the HTTP request
547                         * @param corsPreflight         the CORS preflight request data
548                         * @param corsPreflightResponse the data that should be included in this CORS preflight response
549                         * @return the response to be sent over the wire
550                         */
551                        @NonNull
552                        MarshaledResponse handle(@NonNull Request request,
553                                                                                                                         @NonNull CorsPreflight corsPreflight,
554                                                                                                                         @NonNull CorsPreflightResponse corsPreflightResponse);
555                }
556
557                /**
558                 * Function used to support pluggable implementations of {@link ResponseMarshaler#forCorsPreflightRejected(Request, CorsPreflight)}.
559                 */
560                @FunctionalInterface
561                public interface CorsPreflightRejectedHandler {
562                        /**
563                         * Prepares a response for "CORS preflight rejected" scenario when your {@link CorsAuthorizer} denies a preflight request.
564                         * <p>
565                         * Detailed documentation is available at <a href="https://www.soklet.com/docs/cors#writing-cors-responses">https://www.soklet.com/docs/cors#writing-cors-responses</a>.
566                         *
567                         * @param request       the HTTP request
568                         * @param corsPreflight the CORS preflight request data
569                         * @return the response to be sent over the wire
570                         */
571                        @NonNull
572                        MarshaledResponse handle(@NonNull Request request,
573                                                                                                                         @NonNull CorsPreflight corsPreflight);
574                }
575
576                /**
577                 * Function used to support pluggable implementations of {@link ResponseMarshaler#forCorsAllowed(Request, Cors, CorsResponse, MarshaledResponse)}.
578                 */
579                @FunctionalInterface
580                public interface CorsAllowedHandler {
581                        /**
582                         * Applies "CORS is permitted for this request" data to a response.
583                         * <p>
584                         * Invoked for any non-preflight CORS request that your {@link CorsAuthorizer} approves.
585                         * <p>
586                         * This method will normally return a copy of the {@code marshaledResponse} with these headers applied
587                         * based on the values of {@code corsResponse}:
588                         * <ul>
589                         *   <li>{@code Access-Control-Allow-Origin} (required)</li>
590                         *   <li>{@code Access-Control-Allow-Credentials} (optional)</li>
591                         *   <li>{@code Access-Control-Expose-Headers} (optional)</li>
592                         * </ul>
593                         *
594                         * @param request           the HTTP request
595                         * @param cors              the CORS request data
596                         * @param corsResponse      CORS response data to write as specified by {@link CorsAuthorizer}
597                         * @param marshaledResponse the existing response to which we should apply relevant CORS headers
598                         * @return the response to be sent over the wire
599                         */
600                        @NonNull
601                        MarshaledResponse handle(@NonNull Request request,
602                                                                                                                         @NonNull Cors cors,
603                                                                                                                         @NonNull CorsResponse corsResponse,
604                                                                                                                         @NonNull MarshaledResponse marshaledResponse);
605                }
606
607                /**
608                 * Function used to support a pluggable "post-process" hook for any final customization or processing before data goes over the wire.
609                 */
610                @FunctionalInterface
611                public interface PostProcessor {
612                        /**
613                         * Applies an optional "post-process" hook for any final customization or processing before data goes over the wire.
614                         *
615                         * @param marshaledResponse the response data generated by the appropriate handler function, but not yet sent over the wire
616                         * @return the response data (possibly customized) to be sent over the wire
617                         */
618                        @NonNull
619                        MarshaledResponse postProcess(@NonNull MarshaledResponse marshaledResponse);
620                }
621
622                @NonNull
623                Charset charset;
624                @Nullable
625                ResourceMethodHandler resourceMethodHandler;
626                @Nullable
627                NotFoundHandler notFoundHandler;
628                @Nullable
629                MethodNotAllowedHandler methodNotAllowedHandler;
630                @Nullable
631                ContentTooLargeHandler contentTooLargeHandler;
632                @Nullable
633                ServiceUnavailableHandler serviceUnavailableHandler;
634                @Nullable
635                OptionsHandler optionsHandler;
636                @Nullable
637                OptionsSplatHandler optionsSplatHandler;
638                @Nullable
639                ThrowableHandler throwableHandler;
640                @Nullable
641                HeadHandler headHandler;
642                @Nullable
643                CorsPreflightAllowedHandler corsPreflightAllowedHandler;
644                @Nullable
645                CorsPreflightRejectedHandler corsPreflightRejectedHandler;
646                @Nullable
647                CorsAllowedHandler corsAllowedHandler;
648                @Nullable
649                PostProcessor postProcessor;
650
651                private Builder(@NonNull Charset charset) {
652                        requireNonNull(charset);
653                        this.charset = charset;
654                }
655
656                @NonNull
657                public Builder charset(@NonNull Charset charset) {
658                        requireNonNull(charset);
659                        this.charset = charset;
660                        return this;
661                }
662
663                @NonNull
664                public Builder resourceMethodHandler(@Nullable ResourceMethodHandler resourceMethodHandler) {
665                        this.resourceMethodHandler = resourceMethodHandler;
666                        return this;
667                }
668
669                @NonNull
670                public Builder notFoundHandler(@Nullable NotFoundHandler notFoundHandler) {
671                        this.notFoundHandler = notFoundHandler;
672                        return this;
673                }
674
675                @NonNull
676                public Builder methodNotAllowedHandler(@Nullable MethodNotAllowedHandler methodNotAllowedHandler) {
677                        this.methodNotAllowedHandler = methodNotAllowedHandler;
678                        return this;
679                }
680
681                @NonNull
682                public Builder contentTooLargeHandler(@Nullable ContentTooLargeHandler contentTooLargeHandler) {
683                        this.contentTooLargeHandler = contentTooLargeHandler;
684                        return this;
685                }
686
687                @NonNull
688                public Builder serviceUnavailableHandler(@Nullable ServiceUnavailableHandler serviceUnavailableHandler) {
689                        this.serviceUnavailableHandler = serviceUnavailableHandler;
690                        return this;
691                }
692
693                @NonNull
694                public Builder optionsHandler(@Nullable OptionsHandler optionsHandler) {
695                        this.optionsHandler = optionsHandler;
696                        return this;
697                }
698
699                @NonNull
700                public Builder optionsSplatHandler(@Nullable OptionsSplatHandler optionsSplatHandler) {
701                        this.optionsSplatHandler = optionsSplatHandler;
702                        return this;
703                }
704
705                @NonNull
706                public Builder throwableHandler(@Nullable ThrowableHandler throwableHandler) {
707                        this.throwableHandler = throwableHandler;
708                        return this;
709                }
710
711                @NonNull
712                public Builder headHandler(@Nullable HeadHandler headHandler) {
713                        this.headHandler = headHandler;
714                        return this;
715                }
716
717                @NonNull
718                public Builder corsPreflightAllowedHandler(@Nullable CorsPreflightAllowedHandler corsPreflightAllowedHandler) {
719                        this.corsPreflightAllowedHandler = corsPreflightAllowedHandler;
720                        return this;
721                }
722
723                @NonNull
724                public Builder corsPreflightRejectedHandler(@Nullable CorsPreflightRejectedHandler corsPreflightRejectedHandler) {
725                        this.corsPreflightRejectedHandler = corsPreflightRejectedHandler;
726                        return this;
727                }
728
729                @NonNull
730                public Builder corsAllowedHandler(@Nullable CorsAllowedHandler corsAllowedHandler) {
731                        this.corsAllowedHandler = corsAllowedHandler;
732                        return this;
733                }
734
735                @NonNull
736                public Builder postProcessor(@Nullable PostProcessor postProcessor) {
737                        this.postProcessor = postProcessor;
738                        return this;
739                }
740
741                @NonNull
742                public ResponseMarshaler build() {
743                        return new DefaultResponseMarshaler(this);
744                }
745        }
746}