001/* 002 * Copyright 2022-2025 Revetware LLC. 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 */ 016 017package com.soklet.core; 018 019import javax.annotation.Nonnull; 020import javax.annotation.Nullable; 021import javax.annotation.concurrent.NotThreadSafe; 022import javax.annotation.concurrent.ThreadSafe; 023import java.util.ArrayList; 024import java.util.List; 025import java.util.Objects; 026import java.util.Optional; 027import java.util.stream.Collectors; 028 029import static java.lang.String.format; 030import static java.util.Objects.requireNonNull; 031 032/** 033 * Encapsulates the results of a request (both logical response and bytes to be sent over the wire), 034 * useful for integration testing via {@link Simulator#performRequest(Request)}. 035 * 036 * @author <a href="https://www.revetkn.com">Mark Allen</a> 037 */ 038@ThreadSafe 039public class RequestResult { 040 @Nonnull 041 private final MarshaledResponse marshaledResponse; 042 @Nullable 043 private final Response response; 044 @Nullable 045 private final CorsPreflightResponse corsPreflightResponse; 046 @Nullable 047 private final ResourceMethod resourceMethod; 048 049 /** 050 * Acquires a builder for {@link RequestResult} instances. 051 * 052 * @param marshaledResponse the bytes that will ultimately be written over the wire 053 * @return the builder 054 */ 055 @Nonnull 056 public static Builder withMarshaledResponse(@Nonnull MarshaledResponse marshaledResponse) { 057 requireNonNull(marshaledResponse); 058 return new Builder(marshaledResponse); 059 } 060 061 /** 062 * Vends a mutable copier seeded with this instance's data, suitable for building new instances. 063 * 064 * @return a copier for this instance 065 */ 066 @Nonnull 067 public Copier copy() { 068 return new Copier(this); 069 } 070 071 protected RequestResult(@Nonnull Builder builder) { 072 requireNonNull(builder); 073 074 this.marshaledResponse = builder.marshaledResponse; 075 this.response = builder.response; 076 this.corsPreflightResponse = builder.corsPreflightResponse; 077 this.resourceMethod = builder.resourceMethod; 078 } 079 080 @Override 081 public String toString() { 082 List<String> components = new ArrayList<>(4); 083 084 components.add(format("marshaledResponse=%s", getMarshaledResponse())); 085 086 Response response = getResponse().orElse(null); 087 088 if (response != null) 089 components.add(format("response=%s", response)); 090 091 CorsPreflightResponse corsPreflightResponse = getCorsPreflightResponse().orElse(null); 092 093 if (corsPreflightResponse != null) 094 components.add(format("corsPreflightResponse=%s", corsPreflightResponse)); 095 096 ResourceMethod resourceMethod = getResourceMethod().orElse(null); 097 098 if (resourceMethod != null) 099 components.add(format("resourceMethod=%s", resourceMethod)); 100 101 return format("%s{%s}", getClass().getSimpleName(), components.stream().collect(Collectors.joining(", "))); 102 } 103 104 @Override 105 public boolean equals(@Nullable Object object) { 106 if (this == object) 107 return true; 108 109 if (!(object instanceof RequestResult requestResult)) 110 return false; 111 112 return Objects.equals(getMarshaledResponse(), requestResult.getMarshaledResponse()) 113 && Objects.equals(getResponse(), requestResult.getResponse()) 114 && Objects.equals(getCorsPreflightResponse(), requestResult.getCorsPreflightResponse()) 115 && Objects.equals(getResourceMethod(), requestResult.getResourceMethod()); 116 } 117 118 @Override 119 public int hashCode() { 120 return Objects.hash(getMarshaledResponse(), getResponse(), getCorsPreflightResponse(), getResourceMethod()); 121 } 122 123 /** 124 * The final representation of the response to be written over the wire. 125 * 126 * @return the response to be written over the wire 127 */ 128 @Nonnull 129 public MarshaledResponse getMarshaledResponse() { 130 return this.marshaledResponse; 131 } 132 133 /** 134 * The logical response, determined by the return value of the <em>Resource Method</em> (if available). 135 * 136 * @return the logical response 137 */ 138 @Nonnull 139 public Optional<Response> getResponse() { 140 return Optional.ofNullable(this.response); 141 } 142 143 /** 144 * The CORS preflight logical response, if applicable for the request. 145 * 146 * @return the CORS preflight logical response 147 */ 148 @Nonnull 149 public Optional<CorsPreflightResponse> getCorsPreflightResponse() { 150 return Optional.ofNullable(this.corsPreflightResponse); 151 } 152 153 /** 154 * The <em>Resource Method</em> that handled the request, if available. 155 * 156 * @return the <em>Resource Method</em> that handled the request 157 */ 158 @Nonnull 159 public Optional<ResourceMethod> getResourceMethod() { 160 return Optional.ofNullable(this.resourceMethod); 161 } 162 163 /** 164 * Builder used to construct instances of {@link RequestResult} via {@link RequestResult#withMarshaledResponse(MarshaledResponse)}. 165 * <p> 166 * This class is intended for use by a single thread. 167 * 168 * @author <a href="https://www.revetkn.com">Mark Allen</a> 169 */ 170 @NotThreadSafe 171 public static class Builder { 172 @Nonnull 173 private MarshaledResponse marshaledResponse; 174 @Nullable 175 private Response response; 176 @Nullable 177 private CorsPreflightResponse corsPreflightResponse; 178 @Nullable 179 private ResourceMethod resourceMethod; 180 181 protected Builder(@Nonnull MarshaledResponse marshaledResponse) { 182 requireNonNull(marshaledResponse); 183 this.marshaledResponse = marshaledResponse; 184 } 185 186 @Nonnull 187 public Builder marshaledResponse(@Nonnull MarshaledResponse marshaledResponse) { 188 requireNonNull(marshaledResponse); 189 this.marshaledResponse = marshaledResponse; 190 return this; 191 } 192 193 @Nonnull 194 public Builder response(@Nullable Response response) { 195 this.response = response; 196 return this; 197 } 198 199 @Nonnull 200 public Builder corsPreflightResponse(@Nullable CorsPreflightResponse corsPreflightResponse) { 201 this.corsPreflightResponse = corsPreflightResponse; 202 return this; 203 } 204 205 @Nonnull 206 public Builder resourceMethod(@Nullable ResourceMethod resourceMethod) { 207 this.resourceMethod = resourceMethod; 208 return this; 209 } 210 211 @Nonnull 212 public RequestResult build() { 213 return new RequestResult(this); 214 } 215 } 216 217 /** 218 * Builder used to copy instances of {@link RequestResult} via {@link RequestResult#copy()}. 219 * <p> 220 * This class is intended for use by a single thread. 221 * 222 * @author <a href="https://www.revetkn.com">Mark Allen</a> 223 */ 224 @NotThreadSafe 225 public static class Copier { 226 @Nonnull 227 private final Builder builder; 228 229 Copier(@Nonnull RequestResult requestResult) { 230 requireNonNull(requestResult); 231 232 this.builder = new Builder(requestResult.getMarshaledResponse()) 233 .response(requestResult.getResponse().orElse(null)) 234 .corsPreflightResponse(requestResult.getCorsPreflightResponse().orElse(null)) 235 .resourceMethod(requestResult.getResourceMethod().orElse(null)); 236 } 237 238 @Nonnull 239 public Copier marshaledResponse(@Nonnull MarshaledResponse marshaledResponse) { 240 requireNonNull(marshaledResponse); 241 this.builder.marshaledResponse(marshaledResponse); 242 return this; 243 } 244 245 @Nonnull 246 public Copier response(@Nullable Response response) { 247 this.builder.response(response); 248 return this; 249 } 250 251 @Nonnull 252 public Copier corsPreflightResponse(@Nullable CorsPreflightResponse corsPreflightResponse) { 253 this.builder.corsPreflightResponse(corsPreflightResponse); 254 return this; 255 } 256 257 @Nonnull 258 public Copier resourceMethod(@Nullable ResourceMethod resourceMethod) { 259 this.builder.resourceMethod(resourceMethod); 260 return this; 261 } 262 263 @Nonnull 264 public RequestResult finish() { 265 return this.builder.build(); 266 } 267 } 268}