001/*
002 * Copyright © 2025 CUI-OpenSource-Software (info@cuioss.de)
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 */
016package de.cuioss.http.client.handler;
017
018import lombok.Getter;
019import lombok.RequiredArgsConstructor;
020
021/**
022 * HTTP status code classification enum based on RFC 7231 status code families.
023 *
024 * <p>This enum provides a type-safe way to classify HTTP status codes into their standard
025 * families as defined by RFC 7231. It includes utility methods for checking status code
026 * categories and determining appropriate response handling strategies.</p>
027 *
028 * <h3>Design Principles</h3>
029 * <ul>
030 *   <li><strong>RFC Compliance</strong> - Follows RFC 7231 HTTP status code definitions</li>
031 *   <li><strong>Type Safety</strong> - Enum-based classification prevents invalid categories</li>
032 *   <li><strong>Utility Methods</strong> - Convenient static methods for common checks</li>
033 *   <li><strong>Range Validation</strong> - Validates status codes against standard ranges</li>
034 * </ul>
035 *
036 * <h3>Status Code Families</h3>
037 * <ul>
038 *   <li><strong>1xx Informational</strong> - Request received, continuing process (100-199)</li>
039 *   <li><strong>2xx Success</strong> - Action successfully received, understood, and accepted (200-299)</li>
040 *   <li><strong>3xx Redirection</strong> - Further action must be taken to complete request (300-399)</li>
041 *   <li><strong>4xx Client Error</strong> - Request contains bad syntax or cannot be fulfilled (400-499)</li>
042 *   <li><strong>5xx Server Error</strong> - Server failed to fulfill apparently valid request (500-599)</li>
043 *   <li><strong>Unknown</strong> - Status codes outside standard ranges or error conditions</li>
044 * </ul>
045 *
046 * <h3>Usage Examples</h3>
047 * <pre>
048 * // Classify status codes
049 * HttpStatusFamily family = HttpStatusFamily.fromStatusCode(200);
050 * assert family == HttpStatusFamily.SUCCESS;
051 *
052 * // Check for success
053 * if (HttpStatusFamily.isSuccess(response.statusCode())) {
054 *     // Handle successful response
055 * }
056 *
057 * // Check for errors
058 * if (HttpStatusFamily.isClientError(response.statusCode())) {
059 *     // Handle client error (4xx)
060 * } else if (HttpStatusFamily.isServerError(response.statusCode())) {
061 *     // Handle server error (5xx)
062 * }
063 *
064 * // Validate status code range
065 * if (HttpStatusFamily.isValid(statusCode)) {
066 *     // Process valid HTTP status code
067 * }
068 *
069 * // Family-specific checks
070 * HttpStatusFamily family = HttpStatusFamily.REDIRECTION;
071 * if (family.contains(302)) {
072 *     // Handle redirection logic
073 * }
074 * </pre>
075 *
076 * <h3>Common Status Codes by Family</h3>
077 * <ul>
078 *   <li><strong>2xx Success:</strong> 200 OK, 201 Created, 204 No Content</li>
079 *   <li><strong>3xx Redirection:</strong> 301 Moved Permanently, 302 Found, 304 Not Modified</li>
080 *   <li><strong>4xx Client Error:</strong> 400 Bad Request, 401 Unauthorized, 404 Not Found</li>
081 *   <li><strong>5xx Server Error:</strong> 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable</li>
082 * </ul>
083 *
084 * @since 1.0
085 * @see HttpHandler
086 */
087@RequiredArgsConstructor
088public enum HttpStatusFamily {
089
090    /**
091     * 1xx: Informational - Request received, a continuing process.
092     */
093    INFORMATIONAL(100, 199, "Informational"),
094
095    /**
096     * 2xx: Success - The action was successfully received, understood, and accepted.
097     */
098    SUCCESS(200, 299, "Success"),
099
100    /**
101     * 3xx: Redirection - Further action needs to be taken to complete the request.
102     */
103    REDIRECTION(300, 399, "Redirection"),
104
105    /**
106     * 4xx: Client Error - The request contains bad syntax or cannot be fulfilled.
107     */
108    CLIENT_ERROR(400, 499, "Client Error"),
109
110    /**
111     * 5xx: Server Error - The server failed to fulfill an apparently valid request.
112     */
113    SERVER_ERROR(500, 599, "Server Error"),
114
115    /**
116     * Unknown - Used for status codes outside the standard ranges or for error conditions.
117     */
118    UNKNOWN(-1, -1, "Unknown");
119
120    @Getter private final int minCode;
121    @Getter private final int maxCode;
122    @Getter private final String description;
123
124    /**
125     * Checks if the given status code belongs to this family.
126     *
127     * @param statusCode the HTTP status code to check
128     * @return true if the status code belongs to this family, false otherwise
129     */
130    public boolean contains(int statusCode) {
131        if (this == UNKNOWN) {
132            return statusCode < 100 || statusCode > 599;
133        }
134        return statusCode >= minCode && statusCode <= maxCode;
135    }
136
137    /**
138     * Gets the HTTP status code family for the given status code.
139     *
140     * @param statusCode the HTTP status code
141     * @return the corresponding HttpStatusFamily family
142     */
143    public static HttpStatusFamily fromStatusCode(int statusCode) {
144        for (HttpStatusFamily family : values()) {
145            if (family.contains(statusCode)) {
146                return family;
147            }
148        }
149        return UNKNOWN;
150    }
151
152    /**
153     * Checks if the given status code indicates a successful response (2xx).
154     *
155     * @param statusCode the HTTP status code to check
156     * @return true if the status code indicates success, false otherwise
157     */
158    public static boolean isSuccess(int statusCode) {
159        return SUCCESS.contains(statusCode);
160    }
161
162    /**
163     * Checks if the given status code indicates a client error (4xx).
164     *
165     * @param statusCode the HTTP status code to check
166     * @return true if the status code indicates a client error, false otherwise
167     */
168    public static boolean isClientError(int statusCode) {
169        return CLIENT_ERROR.contains(statusCode);
170    }
171
172    /**
173     * Checks if the given status code indicates a server error (5xx).
174     *
175     * @param statusCode the HTTP status code to check
176     * @return true if the status code indicates a server error, false otherwise
177     */
178    public static boolean isServerError(int statusCode) {
179        return SERVER_ERROR.contains(statusCode);
180    }
181
182    /**
183     * Checks if the given status code indicates a redirection (3xx).
184     *
185     * @param statusCode the HTTP status code to check
186     * @return true if the status code indicates a redirection, false otherwise
187     */
188    public static boolean isRedirection(int statusCode) {
189        return REDIRECTION.contains(statusCode);
190    }
191
192    /**
193     * Checks if the given status code indicates an informational response (1xx).
194     *
195     * @param statusCode the HTTP status code to check
196     * @return true if the status code indicates an informational response, false otherwise
197     */
198    public static boolean isInformational(int statusCode) {
199        return INFORMATIONAL.contains(statusCode);
200    }
201
202    /**
203     * Checks if the given status code is a valid HTTP status code (100-599).
204     *
205     * @param statusCode the HTTP status code to check
206     * @return true if the status code is valid, false otherwise
207     */
208    public static boolean isValid(int statusCode) {
209        return statusCode >= 100 && statusCode <= 599;
210    }
211
212    @Override
213    public String toString() {
214        if (this == UNKNOWN) {
215            return description;
216        }
217        return "%s (%d-%d)".formatted(description, minCode, maxCode);
218    }
219}