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.withServer(
122 *   Server.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         * 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>.
180         *
181         * @param request        the HTTP request
182         * @param resourceMethod the <em>Resource Method</em> that would have handled the request, if available
183         * @return the response to be sent over the wire
184         */
185        @NonNull
186        MarshaledResponse forContentTooLarge(@NonNull Request request,
187                                                                                                                                                         @Nullable ResourceMethod resourceMethod);
188
189        /**
190         * 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>.
191         * <p>
192         * 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>.
193         *
194         * @param request        the HTTP request
195         * @param resourceMethod the <em>Resource Method</em> that would have handled the request, if available
196         * @return the response to be sent over the wire
197         */
198        @NonNull
199        MarshaledResponse forServiceUnavailable(@NonNull Request request,
200                                                                                                                                                                        @Nullable ResourceMethod resourceMethod);
201
202        /**
203         * Prepares a response for an HTTP {@code OPTIONS} request.
204         * <p>
205         * Note that CORS preflight responses are handled specially by {@link #forCorsPreflightAllowed(Request, CorsPreflight, CorsPreflightResponse)}
206         * and {@link #forCorsPreflightRejected(Request, CorsPreflight)} - not this method.
207         * <p>
208         * 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>.
209         *
210         * @param request            the HTTP request
211         * @param allowedHttpMethods appropriate HTTP methods to write to the {@code Allow} response header
212         * @return the response to be sent over the wire
213         */
214        @NonNull
215        MarshaledResponse forOptions(@NonNull Request request,
216                                                                                                                         @NonNull Set<@NonNull HttpMethod> allowedHttpMethods);
217
218        /**
219         * Prepares a response for an HTTP {@code OPTIONS *} (colloquially, "{@code OPTIONS} Splat") request.
220         * <p>
221         * 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.
222         * <p>
223         * 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>.
224         *
225         * @param request the HTTP request
226         * @return the response to be sent over the wire
227         */
228        @NonNull
229        MarshaledResponse forOptionsSplat(@NonNull Request request);
230
231        /**
232         * Prepares a response for scenarios in which an uncaught exception is encountered.
233         * <p>
234         * 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>.
235         *
236         * @param request        the HTTP request
237         * @param throwable      the exception that was thrown
238         * @param resourceMethod the <em>Resource Method</em> that would have handled the request, if available
239         * @return the response to be sent over the wire
240         */
241        @NonNull
242        MarshaledResponse forThrowable(@NonNull Request request,
243                                                                                                                                 @NonNull Throwable throwable,
244                                                                                                                                 @Nullable ResourceMethod resourceMethod);
245
246        /**
247         * Prepares a response for an HTTP {@code HEAD} request.
248         * <p>
249         * 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>.
250         *
251         * @param request                    the HTTP request
252         * @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)
253         * @return the response to be sent over the wire
254         */
255        @NonNull
256        MarshaledResponse forHead(@NonNull Request request,
257                                                                                                                @NonNull MarshaledResponse getMethodMarshaledResponse);
258
259        /**
260         * Prepares a response for "CORS preflight allowed" scenario when your {@link CorsAuthorizer} approves a preflight request.
261         * <p>
262         * 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>.
263         *
264         * @param request               the HTTP request
265         * @param corsPreflight         the CORS preflight request data
266         * @param corsPreflightResponse the data that should be included in this CORS preflight response
267         * @return the response to be sent over the wire
268         */
269        @NonNull
270        MarshaledResponse forCorsPreflightAllowed(@NonNull Request request,
271                                                                                                                                                                                @NonNull CorsPreflight corsPreflight,
272                                                                                                                                                                                @NonNull CorsPreflightResponse corsPreflightResponse);
273
274        /**
275         * Prepares a response for "CORS preflight rejected" scenario when your {@link CorsAuthorizer} denies a preflight request.
276         * <p>
277         * 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>.
278         *
279         * @param request       the HTTP request
280         * @param corsPreflight the CORS preflight request data
281         * @return the response to be sent over the wire
282         */
283        @NonNull
284        MarshaledResponse forCorsPreflightRejected(@NonNull Request request,
285                                                                                                                                                                                 @NonNull CorsPreflight corsPreflight);
286
287        /**
288         * Applies "CORS is permitted for this request" data to a response.
289         * <p>
290         * Invoked for any non-preflight CORS request that your {@link CorsAuthorizer} approves.
291         * <p>
292         * This method will normally return a copy of the {@code marshaledResponse} with these headers applied
293         * based on the values of {@code corsResponse}:
294         * <ul>
295         *   <li>{@code Access-Control-Allow-Origin} (required)</li>
296         *   <li>{@code Access-Control-Allow-Credentials} (optional)</li>
297         *   <li>{@code Access-Control-Expose-Headers} (optional)</li>
298         * </ul>
299         *
300         * @param request           the HTTP request
301         * @param cors              the CORS request data
302         * @param corsResponse      CORS response data to write as specified by {@link CorsAuthorizer}
303         * @param marshaledResponse the existing response to which we should apply relevant CORS headers
304         * @return the response to be sent over the wire
305         */
306        @NonNull
307        MarshaledResponse forCorsAllowed(@NonNull Request request,
308                                                                                                                                         @NonNull Cors cors,
309                                                                                                                                         @NonNull CorsResponse corsResponse,
310                                                                                                                                         @NonNull MarshaledResponse marshaledResponse);
311
312        /**
313         * Acquires a threadsafe {@link ResponseMarshaler} with a reasonable "out of the box" configuration that uses UTF-8 to write character data.
314         * <p>
315         * The returned instance is guaranteed to be a JVM-wide singleton.
316         *
317         * @return a UTF-8 {@code ResponseMarshaler} with default settings
318         */
319        @NonNull
320        static ResponseMarshaler defaultInstance() {
321                return DefaultResponseMarshaler.defaultInstance();
322        }
323
324        /**
325         * Acquires a builder for a default {@link ResponseMarshaler} implementation that uses {@link java.nio.charset.StandardCharsets#UTF_8} encoding.
326         *
327         * @return a {@code ResponseMarshaler} builder
328         */
329        @NonNull
330        static Builder builder() {
331                return new Builder(StandardCharsets.UTF_8);
332        }
333
334        /**
335         * Acquires a builder for a default {@link ResponseMarshaler} implementation.
336         *
337         * @param charset the default charset to use when writing response data
338         * @return a {@code ResponseMarshaler} builder
339         */
340        @NonNull
341        static Builder withCharset(@NonNull Charset charset) {
342                requireNonNull(charset);
343                return new Builder(charset);
344        }
345
346        /**
347         * Builder used to construct a standard implementation of {@link ResponseMarshaler}.
348         * <p>
349         * This class is intended for use by a single thread.
350         *
351         * @author <a href="https://www.revetkn.com">Mark Allen</a>
352         */
353        @NotThreadSafe
354        final class Builder {
355                /**
356                 * Function used to support pluggable implementations of {@link ResponseMarshaler#forResourceMethod(Request, Response, ResourceMethod)}.
357                 */
358                @FunctionalInterface
359                public interface ResourceMethodHandler {
360                        /**
361                         * Prepares a response for the scenario in which the request was matched to a <em>Resource Method</em> and executed non-exceptionally.
362                         * <p>
363                         * 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>.
364                         *
365                         * @param request        the HTTP request
366                         * @param response       the response provided by the <em>Resource Method</em> that handled the request
367                         * @param resourceMethod the <em>Resource Method</em> that handled the request
368                         * @return the response to be sent over the wire
369                         */
370                        @NonNull
371                        MarshaledResponse handle(@NonNull Request request,
372                                                                                                                         @NonNull Response response,
373                                                                                                                         @NonNull ResourceMethod resourceMethod);
374                }
375
376                /**
377                 * Function used to support pluggable implementations of {@link ResponseMarshaler#forNotFound(Request)}.
378                 */
379                @FunctionalInterface
380                public interface NotFoundHandler {
381                        /**
382                         * Prepares a response for a request that triggers an
383                         * <a href="https://httpwg.org/specs/rfc9110.html#status.404">HTTP 404 Not Found</a>.
384                         * <p>
385                         * 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>.
386                         *
387                         * @param request the HTTP request
388                         * @return the response to be sent over the wire
389                         */
390                        @NonNull
391                        MarshaledResponse handle(@NonNull Request request);
392                }
393
394                /**
395                 * Function used to support pluggable implementations of {@link ResponseMarshaler#forMethodNotAllowed(Request, Set)}.
396                 */
397                @FunctionalInterface
398                public interface MethodNotAllowedHandler {
399                        /**
400                         * Prepares a response for a request that triggers an
401                         * <a href="https://httpwg.org/specs/rfc9110.html#status.405">HTTP 405 Method Not Allowed</a>.
402                         * <p>
403                         * 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>.
404                         *
405                         * @param request            the HTTP request
406                         * @param allowedHttpMethods appropriate HTTP methods to write to the {@code Allow} response header
407                         * @return the response to be sent over the wire
408                         */
409                        @NonNull
410                        MarshaledResponse handle(@NonNull Request request,
411                                                                                                                         @NonNull Set<@NonNull HttpMethod> allowedHttpMethods);
412                }
413
414                /**
415                 * Function used to support pluggable implementations of {@link ResponseMarshaler#forContentTooLarge(Request, ResourceMethod)}.
416                 */
417                @FunctionalInterface
418                public interface ContentTooLargeHandler {
419                        /**
420                         * 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>.
421                         * <p>
422                         * 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>.
423                         *
424                         * @param request        the HTTP request
425                         * @param resourceMethod the <em>Resource Method</em> that would have handled the request, if available
426                         * @return the response to be sent over the wire
427                         */
428                        @NonNull
429                        MarshaledResponse handle(@NonNull Request request,
430                                                                                                                         @Nullable ResourceMethod resourceMethod);
431                }
432
433                /**
434                 * Function used to support pluggable implementations of {@link ResponseMarshaler#forServiceUnavailable(Request, ResourceMethod)}.
435                 */
436                @FunctionalInterface
437                public interface ServiceUnavailableHandler {
438                        /**
439                         * Prepares a response for a request that triggers an <a href="https://httpwg.org/specs/rfc9110.html#status.503">HTTP 503 Service Unavailable</a>.
440                         * <p>
441                         * 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>.
442                         *
443                         * @param request        the HTTP request
444                         * @param resourceMethod the <em>Resource Method</em> that would have handled the request, if available
445                         * @return the response to be sent over the wire
446                         */
447                        @NonNull
448                        MarshaledResponse handle(@NonNull Request request,
449                                                                                                                         @Nullable ResourceMethod resourceMethod);
450                }
451
452                /**
453                 * Function used to support pluggable implementations of {@link ResponseMarshaler#forOptions(Request, Set)}.
454                 */
455                @FunctionalInterface
456                public interface OptionsHandler {
457                        /**
458                         * Prepares a response for an HTTP {@code OPTIONS} request.
459                         * <p>
460                         * Note that CORS preflight responses are handled specially by {@link #forCorsPreflightAllowed(Request, CorsPreflight, CorsPreflightResponse)}
461                         * and {@link #forCorsPreflightRejected(Request, CorsPreflight)} - not this method.
462                         * <p>
463                         * 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>.
464                         *
465                         * @param request            the HTTP request
466                         * @param allowedHttpMethods appropriate HTTP methods to write to the {@code Allow} response header
467                         * @return the response to be sent over the wire
468                         */
469                        @NonNull
470                        MarshaledResponse handle(@NonNull Request request,
471                                                                                                                         @NonNull Set<@NonNull HttpMethod> allowedHttpMethods);
472                }
473
474                /**
475                 * Function used to support pluggable implementations of {@link ResponseMarshaler#forOptionsSplat(Request)}.
476                 */
477                @FunctionalInterface
478                public interface OptionsSplatHandler {
479                        /**
480                         * Prepares a response for an HTTP {@code OPTIONS *} (colloquially, "{@code OPTIONS} Splat") request.
481                         * <p>
482                         * 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.
483                         * <p>
484                         * 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>.
485                         *
486                         * @param request the HTTP request
487                         * @return the response to be sent over the wire
488                         */
489                        @NonNull
490                        MarshaledResponse handle(@NonNull Request request);
491                }
492
493                /**
494                 * Function used to support pluggable implementations of {@link ResponseMarshaler#forThrowable(Request, Throwable, ResourceMethod)}.
495                 */
496                @FunctionalInterface
497                public interface ThrowableHandler {
498                        /**
499                         * Prepares a response for scenarios in which an uncaught exception is encountered.
500                         * <p>
501                         * 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>.
502                         *
503                         * @param request        the HTTP request
504                         * @param throwable      the exception that was thrown
505                         * @param resourceMethod the <em>Resource Method</em> that would have handled the request, if available
506                         * @return the response to be sent over the wire
507                         */
508                        @NonNull
509                        MarshaledResponse handle(@NonNull Request request,
510                                                                                                                         @NonNull Throwable throwable,
511                                                                                                                         @Nullable ResourceMethod resourceMethod);
512                }
513
514                /**
515                 * Function used to support pluggable implementations of {@link ResponseMarshaler#forHead(Request, MarshaledResponse)}.
516                 */
517                @FunctionalInterface
518                public interface HeadHandler {
519                        /**
520                         * Prepares a response for an HTTP {@code HEAD} request.
521                         * <p>
522                         * 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>.
523                         *
524                         * @param request                    the HTTP request
525                         * @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)
526                         * @return the response to be sent over the wire
527                         */
528                        @NonNull
529                        MarshaledResponse handle(@NonNull Request request,
530                                                                                                                         @NonNull MarshaledResponse getMethodMarshaledResponse);
531                }
532
533                /**
534                 * Function used to support pluggable implementations of {@link ResponseMarshaler#forCorsPreflightAllowed(Request, CorsPreflight, CorsPreflightResponse)}.
535                 */
536                @FunctionalInterface
537                public interface CorsPreflightAllowedHandler {
538                        /**
539                         * Prepares a response for "CORS preflight allowed" scenario when your {@link CorsAuthorizer} approves a preflight request.
540                         * <p>
541                         * 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>.
542                         *
543                         * @param request               the HTTP request
544                         * @param corsPreflight         the CORS preflight request data
545                         * @param corsPreflightResponse the data that should be included in this CORS preflight response
546                         * @return the response to be sent over the wire
547                         */
548                        @NonNull
549                        MarshaledResponse handle(@NonNull Request request,
550                                                                                                                         @NonNull CorsPreflight corsPreflight,
551                                                                                                                         @NonNull CorsPreflightResponse corsPreflightResponse);
552                }
553
554                /**
555                 * Function used to support pluggable implementations of {@link ResponseMarshaler#forCorsPreflightRejected(Request, CorsPreflight)}.
556                 */
557                @FunctionalInterface
558                public interface CorsPreflightRejectedHandler {
559                        /**
560                         * Prepares a response for "CORS preflight rejected" scenario when your {@link CorsAuthorizer} denies a preflight request.
561                         * <p>
562                         * 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>.
563                         *
564                         * @param request       the HTTP request
565                         * @param corsPreflight the CORS preflight request data
566                         * @return the response to be sent over the wire
567                         */
568                        @NonNull
569                        MarshaledResponse handle(@NonNull Request request,
570                                                                                                                         @NonNull CorsPreflight corsPreflight);
571                }
572
573                /**
574                 * Function used to support pluggable implementations of {@link ResponseMarshaler#forCorsAllowed(Request, Cors, CorsResponse, MarshaledResponse)}.
575                 */
576                @FunctionalInterface
577                public interface CorsAllowedHandler {
578                        /**
579                         * Applies "CORS is permitted for this request" data to a response.
580                         * <p>
581                         * Invoked for any non-preflight CORS request that your {@link CorsAuthorizer} approves.
582                         * <p>
583                         * This method will normally return a copy of the {@code marshaledResponse} with these headers applied
584                         * based on the values of {@code corsResponse}:
585                         * <ul>
586                         *   <li>{@code Access-Control-Allow-Origin} (required)</li>
587                         *   <li>{@code Access-Control-Allow-Credentials} (optional)</li>
588                         *   <li>{@code Access-Control-Expose-Headers} (optional)</li>
589                         * </ul>
590                         *
591                         * @param request           the HTTP request
592                         * @param cors              the CORS request data
593                         * @param corsResponse      CORS response data to write as specified by {@link CorsAuthorizer}
594                         * @param marshaledResponse the existing response to which we should apply relevant CORS headers
595                         * @return the response to be sent over the wire
596                         */
597                        @NonNull
598                        MarshaledResponse handle(@NonNull Request request,
599                                                                                                                         @NonNull Cors cors,
600                                                                                                                         @NonNull CorsResponse corsResponse,
601                                                                                                                         @NonNull MarshaledResponse marshaledResponse);
602                }
603
604                /**
605                 * Function used to support a pluggable "post-process" hook for any final customization or processing before data goes over the wire.
606                 */
607                @FunctionalInterface
608                public interface PostProcessor {
609                        /**
610                         * Applies an optional "post-process" hook for any final customization or processing before data goes over the wire.
611                         *
612                         * @param marshaledResponse the response data generated by the appropriate handler function, but not yet sent over the wire
613                         * @return the response data (possibly customized) to be sent over the wire
614                         */
615                        @NonNull
616                        MarshaledResponse postProcess(@NonNull MarshaledResponse marshaledResponse);
617                }
618
619                @NonNull
620                Charset charset;
621                @Nullable
622                ResourceMethodHandler resourceMethodHandler;
623                @Nullable
624                NotFoundHandler notFoundHandler;
625                @Nullable
626                MethodNotAllowedHandler methodNotAllowedHandler;
627                @Nullable
628                ContentTooLargeHandler contentTooLargeHandler;
629                @Nullable
630                ServiceUnavailableHandler serviceUnavailableHandler;
631                @Nullable
632                OptionsHandler optionsHandler;
633                @Nullable
634                OptionsSplatHandler optionsSplatHandler;
635                @Nullable
636                ThrowableHandler throwableHandler;
637                @Nullable
638                HeadHandler headHandler;
639                @Nullable
640                CorsPreflightAllowedHandler corsPreflightAllowedHandler;
641                @Nullable
642                CorsPreflightRejectedHandler corsPreflightRejectedHandler;
643                @Nullable
644                CorsAllowedHandler corsAllowedHandler;
645                @Nullable
646                PostProcessor postProcessor;
647
648                private Builder(@NonNull Charset charset) {
649                        requireNonNull(charset);
650                        this.charset = charset;
651                }
652
653                @NonNull
654                public Builder charset(@NonNull Charset charset) {
655                        requireNonNull(charset);
656                        this.charset = charset;
657                        return this;
658                }
659
660                @NonNull
661                public Builder resourceMethodHandler(@Nullable ResourceMethodHandler resourceMethodHandler) {
662                        this.resourceMethodHandler = resourceMethodHandler;
663                        return this;
664                }
665
666                @NonNull
667                public Builder notFoundHandler(@Nullable NotFoundHandler notFoundHandler) {
668                        this.notFoundHandler = notFoundHandler;
669                        return this;
670                }
671
672                @NonNull
673                public Builder methodNotAllowedHandler(@Nullable MethodNotAllowedHandler methodNotAllowedHandler) {
674                        this.methodNotAllowedHandler = methodNotAllowedHandler;
675                        return this;
676                }
677
678                @NonNull
679                public Builder contentTooLargeHandler(@Nullable ContentTooLargeHandler contentTooLargeHandler) {
680                        this.contentTooLargeHandler = contentTooLargeHandler;
681                        return this;
682                }
683
684                @NonNull
685                public Builder serviceUnavailableHandler(@Nullable ServiceUnavailableHandler serviceUnavailableHandler) {
686                        this.serviceUnavailableHandler = serviceUnavailableHandler;
687                        return this;
688                }
689
690                @NonNull
691                public Builder optionsHandler(@Nullable OptionsHandler optionsHandler) {
692                        this.optionsHandler = optionsHandler;
693                        return this;
694                }
695
696                @NonNull
697                public Builder optionsSplatHandler(@Nullable OptionsSplatHandler optionsSplatHandler) {
698                        this.optionsSplatHandler = optionsSplatHandler;
699                        return this;
700                }
701
702                @NonNull
703                public Builder throwableHandler(@Nullable ThrowableHandler throwableHandler) {
704                        this.throwableHandler = throwableHandler;
705                        return this;
706                }
707
708                @NonNull
709                public Builder headHandler(@Nullable HeadHandler headHandler) {
710                        this.headHandler = headHandler;
711                        return this;
712                }
713
714                @NonNull
715                public Builder corsPreflightAllowedHandler(@Nullable CorsPreflightAllowedHandler corsPreflightAllowedHandler) {
716                        this.corsPreflightAllowedHandler = corsPreflightAllowedHandler;
717                        return this;
718                }
719
720                @NonNull
721                public Builder corsPreflightRejectedHandler(@Nullable CorsPreflightRejectedHandler corsPreflightRejectedHandler) {
722                        this.corsPreflightRejectedHandler = corsPreflightRejectedHandler;
723                        return this;
724                }
725
726                @NonNull
727                public Builder corsAllowedHandler(@Nullable CorsAllowedHandler corsAllowedHandler) {
728                        this.corsAllowedHandler = corsAllowedHandler;
729                        return this;
730                }
731
732                @NonNull
733                public Builder postProcessor(@Nullable PostProcessor postProcessor) {
734                        this.postProcessor = postProcessor;
735                        return this;
736                }
737
738                @NonNull
739                public ResponseMarshaler build() {
740                        return new DefaultResponseMarshaler(this);
741                }
742        }
743}