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