1 package com.guinetik.rr.result;
2
3 /**
4 * Represents an API error in the {@link Result} pattern.
5 *
6 * <p>This class encapsulates information about API errors without using exceptions,
7 * providing a functional approach to error handling. It includes the error message,
8 * HTTP status code (when applicable), response body, and error type classification.
9 *
10 * <h2>Error Types</h2>
11 * <ul>
12 * <li>{@link ErrorType#HTTP_ERROR} - Server returned an error status code</li>
13 * <li>{@link ErrorType#NETWORK_ERROR} - Connection or network failure</li>
14 * <li>{@link ErrorType#PARSE_ERROR} - Failed to parse response body</li>
15 * <li>{@link ErrorType#AUTH_ERROR} - Authentication/authorization failure</li>
16 * <li>{@link ErrorType#CONFIG_ERROR} - Client misconfiguration</li>
17 * <li>{@link ErrorType#CIRCUIT_OPEN} - Circuit breaker is open</li>
18 * </ul>
19 *
20 * <h2>Creating Errors</h2>
21 * <pre class="language-java"><code>
22 * // HTTP error with status code
23 * ApiError notFound = ApiError.httpError("User not found", 404, responseBody);
24 *
25 * // Network error
26 * ApiError networkError = ApiError.networkError("Connection refused");
27 *
28 * // Parse error
29 * ApiError parseError = ApiError.parseError("Invalid JSON", responseBody);
30 * </code></pre>
31 *
32 * <h2>Handling Errors</h2>
33 * <pre class="language-java"><code>
34 * Result<User, ApiError> result = client.fluent().get("/users/1", User.class);
35 *
36 * result.ifFailure(error -> {
37 * if (error.isType(ErrorType.HTTP_ERROR)) {
38 * if (error.hasStatusCode(404)) {
39 * System.out.println("User not found");
40 * } else if (error.hasStatusCode(500)) {
41 * System.out.println("Server error");
42 * }
43 * } else if (error.isType(ErrorType.NETWORK_ERROR)) {
44 * System.out.println("Check your connection");
45 * }
46 * });
47 * </code></pre>
48 *
49 * @author guinetik <guinetik@gmail.com>
50 * @see Result
51 * @see com.guinetik.rr.api.FluentApiClient
52 * @since 1.0.0
53 */
54 public class ApiError {
55 private final String message;
56 private final int statusCode;
57 private final String responseBody;
58 private final ErrorType errorType;
59
60 /**
61 * Enum representing different types of errors that can occur.
62 */
63 public enum ErrorType {
64 /** HTTP error from server */
65 HTTP_ERROR,
66
67 /** Network connectivity error */
68 NETWORK_ERROR,
69
70 /** Error parsing response */
71 PARSE_ERROR,
72
73 /** Authentication/authorization error */
74 AUTH_ERROR,
75
76 /** Client configuration error */
77 CONFIG_ERROR,
78
79 /** Circuit breaker is open */
80 CIRCUIT_OPEN,
81
82 /** Unknown error */
83 UNKNOWN
84 }
85
86 /**
87 * Constructs a new ApiError with the specified parameters.
88 *
89 * @param message Error message
90 * @param statusCode HTTP status code (may be 0 for non-HTTP errors)
91 * @param responseBody Response body or null if not available
92 * @param errorType Type of error that occurred
93 */
94 public ApiError(String message, int statusCode, String responseBody, ErrorType errorType) {
95 this.message = message;
96 this.statusCode = statusCode;
97 this.responseBody = responseBody;
98 this.errorType = errorType;
99 }
100
101 /**
102 * Constructs an HTTP error with status code.
103 *
104 * @param message Error message
105 * @param statusCode HTTP status code
106 * @param responseBody Response body
107 * @return A new ApiError representing an HTTP error
108 */
109 public static ApiError httpError(String message, int statusCode, String responseBody) {
110 return new ApiError(message, statusCode, responseBody, ErrorType.HTTP_ERROR);
111 }
112
113 /**
114 * Constructs a network error.
115 *
116 * @param message Error message
117 * @return A new ApiError representing a network error
118 */
119 public static ApiError networkError(String message) {
120 return new ApiError(message, 0, null, ErrorType.NETWORK_ERROR);
121 }
122
123 /**
124 * Constructs a parse error.
125 *
126 * @param message Error message
127 * @param responseBody The response that couldn't be parsed
128 * @return A new ApiError representing a parse error
129 */
130 public static ApiError parseError(String message, String responseBody) {
131 return new ApiError(message, 0, responseBody, ErrorType.PARSE_ERROR);
132 }
133
134 /**
135 * Constructs an authentication error.
136 *
137 * @param message Error message
138 * @param statusCode HTTP status code (typically 401)
139 * @param responseBody Response body
140 * @return A new ApiError representing an authentication error
141 */
142 public static ApiError authError(String message, int statusCode, String responseBody) {
143 return new ApiError(message, statusCode, responseBody, ErrorType.AUTH_ERROR);
144 }
145
146 /**
147 * Constructs a configuration error.
148 *
149 * @param message Error message
150 * @return A new ApiError representing a configuration error
151 */
152 public static ApiError configError(String message) {
153 return new ApiError(message, 0, null, ErrorType.CONFIG_ERROR);
154 }
155
156 /**
157 * Constructs a circuit breaker open error.
158 *
159 * @param message Error message
160 * @return A new ApiError representing a circuit breaker open error
161 */
162 public static ApiError circuitOpenError(String message) {
163 return new ApiError(message, 0, null, ErrorType.CIRCUIT_OPEN);
164 }
165
166 /**
167 * Gets the error message.
168 *
169 * @return The error message
170 */
171 public String getMessage() {
172 return message;
173 }
174
175 /**
176 * Gets the HTTP status code.
177 *
178 * @return The HTTP status code, or 0 if not applicable
179 */
180 public int getStatusCode() {
181 return statusCode;
182 }
183
184 /**
185 * Gets the response body.
186 *
187 * @return The response body, or null if not available
188 */
189 public String getResponseBody() {
190 return responseBody;
191 }
192
193 /**
194 * Gets the error type.
195 *
196 * @return The error type
197 */
198 public ErrorType getErrorType() {
199 return errorType;
200 }
201
202 /**
203 * Checks if this error is of the specified type.
204 *
205 * @param type The error type to check
206 * @return true if this error is of the specified type, false otherwise
207 */
208 public boolean isType(ErrorType type) {
209 return this.errorType == type;
210 }
211
212 /**
213 * Checks if this error is an HTTP error with the specified status code.
214 *
215 * @param statusCode The HTTP status code to check
216 * @return true if this error is an HTTP error with the specified status code
217 */
218 public boolean hasStatusCode(int statusCode) {
219 return this.statusCode == statusCode;
220 }
221
222 @Override
223 public String toString() {
224 StringBuilder sb = new StringBuilder();
225 sb.append(errorType).append(": ").append(message);
226
227 if (statusCode > 0) {
228 sb.append(" (Status: ").append(statusCode).append(")");
229 }
230
231 return sb.toString();
232 }
233 }