View Javadoc
1   package com.guinetik.rr.interceptor;
2   
3   import com.guinetik.rr.http.RocketRestException;
4   import com.guinetik.rr.request.RequestSpec;
5   
6   /**
7    * Interceptor for adding cross-cutting behavior to HTTP request execution.
8    *
9    * <p>Interceptors provide lifecycle hooks that can modify requests before execution,
10   * transform responses after execution, and handle errors (including retry logic).
11   * They form a chain where each interceptor can delegate to the next.
12   *
13   * <h2>Interceptor Chain</h2>
14   * <pre>
15   * Request → [Interceptor 1] → [Interceptor 2] → ... → [HTTP Client] → Response
16   *              ↓ beforeRequest()                           ↓
17   *              ↓ onError() if exception                    ↓
18   *              ← afterResponse() ←←←←←←←←←←←←←←←←←←←←←←←←←←
19   * </pre>
20   *
21   * <h2>Creating an Interceptor</h2>
22   * <pre class="language-java"><code>
23   * public class LoggingInterceptor implements RequestInterceptor {
24   *
25   *     &#64;Override
26   *     public &lt;Req, Res&gt; RequestSpec&lt;Req, Res&gt; beforeRequest(RequestSpec&lt;Req, Res&gt; request) {
27   *         System.out.println("-&gt; " + request.getMethod() + " " + request.getEndpoint());
28   *         return request;
29   *     }
30   *
31   *     &#64;Override
32   *     public &lt;Res&gt; Res afterResponse(Res response, RequestSpec&lt;?, Res&gt; request) {
33   *         System.out.println("&lt;- Response received");
34   *         return response;
35   *     }
36   * }
37   * </code></pre>
38   *
39   * <h2>Retry Interceptor Example</h2>
40   * <pre class="language-java"><code>
41   * public class RetryInterceptor implements RequestInterceptor {
42   *     private final int maxRetries = 3;
43   *
44   *     &#64;Override
45   *     public &lt;Req, Res&gt; Res onError(RocketRestException e, RequestSpec&lt;Req, Res&gt; request,
46   *                                    InterceptorChain chain) throws RocketRestException {
47   *         if (isRetryable(e) &amp;&amp; chain.getRetryCount() &lt; maxRetries) {
48   *             Thread.sleep(1000 * chain.getRetryCount()); // Exponential backoff
49   *             return chain.retry(request);
50   *         }
51   *         throw e;
52   *     }
53   * }
54   * </code></pre>
55   *
56   * <h2>Using Interceptors</h2>
57   * <pre class="language-java"><code>
58   * RocketClient client = RocketClientFactory.builder("https://api.example.com")
59   *     .withInterceptor(new LoggingInterceptor())
60   *     .withInterceptor(new RetryInterceptor(3, 1000))
61   *     .withInterceptor(new MetricsInterceptor())
62   *     .build();
63   * </code></pre>
64   *
65   * @author guinetik &lt;guinetik@gmail.com&gt;
66   * @see InterceptorChain
67   * @see RetryInterceptor
68   * @since 1.1.0
69   */
70  public interface RequestInterceptor {
71  
72      /**
73       * Called before a request is executed.
74       *
75       * <p>This method can modify the request (e.g., add headers, transform body)
76       * or return the same request unchanged. To short-circuit the chain and
77       * prevent execution, throw an exception.
78       *
79       * @param request The request specification
80       * @param <Req> The request body type
81       * @param <Res> The response type
82       * @return The (possibly modified) request to execute
83       */
84      default <Req, Res> RequestSpec<Req, Res> beforeRequest(RequestSpec<Req, Res> request) {
85          return request;
86      }
87  
88      /**
89       * Called after a successful response is received.
90       *
91       * <p>This method can transform the response or perform side effects
92       * like logging or metrics collection.
93       *
94       * @param response The response from the server
95       * @param request The original request specification
96       * @param <Res> The response type
97       * @return The (possibly transformed) response
98       */
99      default <Res> Res afterResponse(Res response, RequestSpec<?, Res> request) {
100         return response;
101     }
102 
103     /**
104      * Called when an exception occurs during request execution.
105      *
106      * <p>This method can:
107      * <ul>
108      *   <li>Retry the request using {@link InterceptorChain#retry(RequestSpec)}</li>
109      *   <li>Transform the exception into a different one</li>
110      *   <li>Recover and return a fallback response</li>
111      *   <li>Rethrow the exception (default behavior)</li>
112      * </ul>
113      *
114      * @param e The exception that occurred
115      * @param request The request that failed
116      * @param chain The interceptor chain (use for retry)
117      * @param <Req> The request body type
118      * @param <Res> The response type
119      * @return A recovered response, or throws an exception
120      * @throws RocketRestException If the error cannot be handled
121      */
122     default <Req, Res> Res onError(RocketRestException e, RequestSpec<Req, Res> request,
123                                     InterceptorChain chain) throws RocketRestException {
124         throw e;
125     }
126 
127     /**
128      * Returns the order of this interceptor in the chain.
129      *
130      * <p>Lower values execute first. Use negative values for interceptors
131      * that must run early (e.g., authentication), and positive values
132      * for interceptors that should run late (e.g., logging).
133      *
134      * <p>Suggested ordering:
135      * <ul>
136      *   <li>-100: Authentication/Authorization</li>
137      *   <li>0: Default (most interceptors)</li>
138      *   <li>100: Retry logic</li>
139      *   <li>200: Logging/Metrics</li>
140      * </ul>
141      *
142      * @return The order value (lower = earlier)
143      */
144     default int getOrder() {
145         return 0;
146     }
147 }