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 * @Override
26 * public <Req, Res> RequestSpec<Req, Res> beforeRequest(RequestSpec<Req, Res> request) {
27 * System.out.println("-> " + request.getMethod() + " " + request.getEndpoint());
28 * return request;
29 * }
30 *
31 * @Override
32 * public <Res> Res afterResponse(Res response, RequestSpec<?, Res> request) {
33 * System.out.println("<- 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 * @Override
45 * public <Req, Res> Res onError(RocketRestException e, RequestSpec<Req, Res> request,
46 * InterceptorChain chain) throws RocketRestException {
47 * if (isRetryable(e) && chain.getRetryCount() < 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 <guinetik@gmail.com>
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 }