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.result;
017
018import de.cuioss.uimodel.result.ResultDetail;
019import de.cuioss.uimodel.result.ResultObject;
020import de.cuioss.uimodel.result.ResultState;
021import lombok.EqualsAndHashCode;
022import lombok.ToString;
023
024import java.io.Serial;
025import java.util.Optional;
026import java.util.function.Function;
027
028/**
029 * HTTP-specific result object that extends the CUI result pattern with HTTP protocol semantics.
030 *
031 * <h2>Key Features</h2>
032 * <ul>
033 *   <li>CUI result pattern integration (VALID/WARNING/ERROR states)</li>
034 *   <li>ETag support for efficient caching</li>
035 *   <li>HTTP status code tracking</li>
036 *   <li>Fluent API for common HTTP operations</li>
037 *   <li>Built-in fallback and default result handling</li>
038 * </ul>
039 *
040 * <h2>Usage Patterns</h2>
041 *
042 * <h3>1. Basic HTTP Result Handling</h3>
043 * <pre>
044 * HttpResultObject&lt;String&gt; result = httpHandler.load();
045 *
046 * if (!result.isValid()) {
047 *     // Handle error case with retry logic
048 *     if (result.isRetryable()) {
049 *         scheduleRetry();
050 *     } else {
051 *         result.getResultDetail().ifPresent(detail ->
052 *             logger.error(detail.getDetail().getDisplayName()));
053 *         return result.copyStateAndDetails(fallbackContent);
054 *     }
055 * }
056 *
057 * // Process successful result
058 * String content = result.getResult();
059 * String etag = result.getETag().orElse("");
060 * processContent(content, etag);
061 * </pre>
062 *
063 * <h3>2. Factory Methods</h3>
064 * <pre>
065 * // Successful HTTP operations
066 * HttpResultObject&lt;String&gt; fresh = HttpResultObject.success(content, etag, 200);
067 * HttpResultObject&lt;String&gt; cached = HttpResultObject.success(cachedContent, etag, 304);
068 *
069 * // Error with fallback content
070 * HttpResultObject&lt;String&gt; error = HttpResultObject.error(fallback, errorCode, detail);
071 * </pre>
072 *
073 * <h2>CUI Result Pattern Integration</h2>
074 * <ul>
075 *   <li><strong>VALID</strong>: HTTP operation succeeded (fresh or cached content)</li>
076 *   <li><strong>WARNING</strong>: Degraded state (stale cache, partial recovery)</li>
077 *   <li><strong>ERROR</strong>: Operation failed (with optional fallback content)</li>
078 * </ul>
079 *
080 * @param <T> The type of the HTTP response content
081 * @author Implementation for JWT HTTP operations
082 * @see HttpResultState
083 * @see de.cuioss.uimodel.result.ResultDetail
084 * @see HttpErrorCategory
085 * @since 1.0
086 */
087@ToString(callSuper = true, doNotUseGetters = true)
088@EqualsAndHashCode(callSuper = true, doNotUseGetters = true)
089public class HttpResultObject<T> extends ResultObject<T> {
090
091    @Serial private static final long serialVersionUID = 1L;
092
093    /**
094     * HTTP ETag from the response for caching optimization.
095     */
096    private final String etag;
097
098    /**
099     * HTTP status code from the response.
100     */
101    private final Integer httpStatus;
102
103    /**
104     * HTTP-specific error classification.
105     */
106    private final HttpErrorCategory httpErrorCategory;
107
108
109    /**
110     * Comprehensive constructor for HTTP result objects.
111     *
112     * @param result the wrapped result value
113     * @param state the result state (using CUI base types)
114     * @param resultDetail result detail
115     * @param httpErrorCategory HTTP-specific error code
116     * @param etag optional HTTP ETag
117     * @param httpStatus optional HTTP status code
118     */
119    public HttpResultObject(T result, ResultState state, ResultDetail resultDetail,
120                            HttpErrorCategory httpErrorCategory, String etag, Integer httpStatus) {
121        super(result, state, resultDetail, httpErrorCategory);
122        this.etag = etag;
123        this.httpStatus = httpStatus;
124        this.httpErrorCategory = httpErrorCategory;
125    }
126
127    /**
128     * Copy constructor that transforms the result while preserving HTTP metadata.
129     *
130     * @param previousResult the previous HTTP result to copy from
131     * @param mapper function to transform the result value
132     * @param validDefault default value if previous result was invalid
133     * @param <R> type of the previous result
134     */
135    public <R> HttpResultObject(HttpResultObject<R> previousResult, Function<R, T> mapper, T validDefault) {
136        super(previousResult, mapper, validDefault);
137        this.etag = previousResult.etag;
138        this.httpStatus = previousResult.httpStatus;
139        this.httpErrorCategory = previousResult.httpErrorCategory;
140    }
141
142
143    // === HTTP Metadata Access ===
144
145    /**
146     * Gets the HTTP ETag if present.
147     *
148     * @return Optional containing ETag, or empty if not available
149     */
150    public Optional<String> getETag() {
151        return Optional.ofNullable(etag);
152    }
153
154    /**
155     * Gets the HTTP status code if present.
156     *
157     * @return Optional containing status code, or empty if not available
158     */
159    public Optional<Integer> getHttpStatus() {
160        return Optional.ofNullable(httpStatus);
161    }
162
163    /**
164     * Gets the HTTP-specific error code if present.
165     *
166     * @return Optional containing HTTP error code, or empty if not available
167     */
168    public Optional<HttpErrorCategory> getHttpErrorCategory() {
169        return Optional.ofNullable(httpErrorCategory);
170    }
171
172    /**
173     * Checks if the error condition is retryable.
174     * Only meaningful when the result is not valid.
175     *
176     * @return true if error is retryable, false otherwise
177     */
178    public boolean isRetryable() {
179        return getHttpErrorCategory().map(HttpErrorCategory::isRetryable).orElse(false);
180    }
181
182
183    // === Transformation Methods ===
184
185    /**
186     * Transforms this result to a different type while preserving HTTP metadata.
187     *
188     * @param mapper function to transform the result value
189     * @param defaultValue default value if this result is invalid
190     * @param <U> target result type
191     * @return new HttpResultObject with transformed value
192     */
193    public <U> HttpResultObject<U> map(Function<T, U> mapper, U defaultValue) {
194        return new HttpResultObject<>(this, mapper, defaultValue);
195    }
196
197    /**
198     * Creates a new result object copying state and details from this one.
199     * Useful for error propagation without changing the result type.
200     *
201     * @param newResult the new result value
202     * @param <U> new result type
203     * @return new HttpResultObject with copied state and details
204     */
205    public <U> HttpResultObject<U> copyStateAndDetails(U newResult) {
206        return new HttpResultObject<>(
207                newResult,
208                getState(),
209                getResultDetail().orElse(null),
210                httpErrorCategory,
211                etag,
212                httpStatus
213        );
214    }
215
216
217    // === Factory Methods ===
218
219    /**
220     * Creates a successful HTTP result.
221     *
222     * @param result the result content
223     * @param etag optional ETag
224     * @param httpStatus HTTP status code
225     * @param <U> result type
226     * @return HttpResultObject in VALID state
227     */
228    public static <U> HttpResultObject<U> success(U result, String etag, int httpStatus) {
229        return new HttpResultObject<>(
230                result,
231                ResultState.VALID,
232                null,
233                null,
234                etag,
235                httpStatus
236        );
237    }
238
239    /**
240     * Creates an error result with optional fallback content.
241     *
242     * @param fallbackResult optional fallback content
243     * @param httpErrorCategory the error classification
244     * @param detail error details
245     * @param <U> result type
246     * @return HttpResultObject in ERROR state
247     */
248    public static <U> HttpResultObject<U> error(U fallbackResult, HttpErrorCategory httpErrorCategory, ResultDetail detail) {
249        return new HttpResultObject<>(
250                fallbackResult,
251                ResultState.ERROR,
252                detail,
253                httpErrorCategory,
254                null,
255                null
256        );
257    }
258}