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<String> 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<String> fresh = HttpResultObject.success(content, etag, 200); 067 * HttpResultObject<String> cached = HttpResultObject.success(cachedContent, etag, 304); 068 * 069 * // Error with fallback content 070 * HttpResultObject<String> 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}