Files
github-api/src/main/java/org/kohsuke/github/GitHubResponse.java
2021-01-04 01:30:59 -08:00

320 lines
9.2 KiB
Java

package org.kohsuke.github;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.InjectableValues;
import com.fasterxml.jackson.databind.JsonMappingException;
import org.apache.commons.io.IOUtils;
import org.kohsuke.github.function.FunctionThrows;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Array;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
/**
* A GitHubResponse
* <p>
* A {@link GitHubResponse} generated by from sending a {@link GitHubRequest} to a {@link GitHubClient}.
* </p>
*
* @param <T>
* the type of the data parsed from the body of a {@link ResponseInfo}.
*/
class GitHubResponse<T> {
private static final Logger LOGGER = Logger.getLogger(GitHubResponse.class.getName());
private final int statusCode;
@Nonnull
private final GitHubRequest request;
@Nonnull
private final Map<String, List<String>> headers;
@CheckForNull
private final T body;
GitHubResponse(GitHubResponse<T> response, @CheckForNull T body) {
this.statusCode = response.statusCode();
this.request = response.request();
this.headers = response.headers();
this.body = body;
}
GitHubResponse(ResponseInfo responseInfo, @CheckForNull T body) {
this.statusCode = responseInfo.statusCode();
this.request = responseInfo.request();
this.headers = responseInfo.headers();
this.body = body;
}
/**
* Parses a {@link ResponseInfo} body into a new instance of {@link T}.
*
* @param responseInfo
* response info to parse.
* @param type
* the type to be constructed.
* @param <T>
* the type
* @return a new instance of {@link T}.
* @throws IOException
* if there is an I/O Exception.
*/
@CheckForNull
static <T> T parseBody(ResponseInfo responseInfo, Class<T> type) throws IOException {
if (responseInfo.statusCode() == HttpURLConnection.HTTP_NO_CONTENT) {
if (type != null && type.isArray()) {
// no content for array should be empty array
return type.cast(Array.newInstance(type.getComponentType(), 0));
} else {
// no content for object should be null
return null;
}
}
String data = responseInfo.getBodyAsString();
try {
InjectableValues.Std inject = new InjectableValues.Std();
inject.addValue(ResponseInfo.class, responseInfo);
return GitHubClient.getMappingObjectReader(responseInfo).forType(type).readValue(data);
} catch (JsonMappingException | JsonParseException e) {
String message = "Failed to deserialize: " + data;
LOGGER.log(Level.FINE, message);
throw e;
}
}
/**
* Parses a {@link ResponseInfo} body into a new instance of {@link T}.
*
* @param responseInfo
* response info to parse.
* @param instance
* the object to fill with data parsed from body
* @param <T>
* the type
* @return a new instance of {@link T}.
* @throws IOException
* if there is an I/O Exception.
*/
@CheckForNull
static <T> T parseBody(ResponseInfo responseInfo, T instance) throws IOException {
String data = responseInfo.getBodyAsString();
try {
return GitHubClient.getMappingObjectReader(responseInfo).withValueToUpdate(instance).readValue(data);
} catch (JsonMappingException | JsonParseException e) {
String message = "Failed to deserialize: " + data;
LOGGER.log(Level.FINE, message);
throw e;
}
}
/**
* The {@link URL} for this response.
*
* @return the {@link URL} for this response.
*/
@Nonnull
public URL url() {
return request.url();
}
/**
* The {@link GitHubRequest} for this response.
*
* @return the {@link GitHubRequest} for this response.
*/
@Nonnull
public GitHubRequest request() {
return request;
}
/**
* The status code for this response.
*
* @return the status code for this response.
*/
public int statusCode() {
return statusCode;
}
/**
* The headers for this response.
*
* @return the headers for this response.
*/
@Nonnull
public Map<String, List<String>> headers() {
return headers;
}
/**
* Gets the value of a header field for this response.
*
* @param name
* the name of the header field.
* @return the value of the header field, or {@code null} if the header isn't set.
*/
@CheckForNull
public String headerField(String name) {
String result = null;
if (headers.containsKey(name)) {
result = headers.get(name).get(0);
}
return result;
}
/**
* The body of the response parsed as a {@link T}
*
* @return body of the response
*/
public T body() {
return body;
}
/**
* Represents a supplier of results that can throw.
*
* @param <T>
* the type of results supplied by this supplier
*/
@FunctionalInterface
interface BodyHandler<T> extends FunctionThrows<ResponseInfo, T, IOException> {
}
/**
* Initial response information supplied to a {@link BodyHandler} when a response is initially received and before
* the body is processed.
*/
static abstract class ResponseInfo implements Closeable {
private static final Comparator<String> nullableCaseInsensitiveComparator = Comparator
.nullsFirst(String.CASE_INSENSITIVE_ORDER);
private final int statusCode;
@Nonnull
private final GitHubRequest request;
@Nonnull
private final Map<String, List<String>> headers;
protected ResponseInfo(@Nonnull GitHubRequest request,
int statusCode,
@Nonnull Map<String, List<String>> headers) {
this.request = request;
this.statusCode = statusCode;
// Response header field names must be case-insensitive.
TreeMap<String, List<String>> caseInsensitiveMap = new TreeMap<>(nullableCaseInsensitiveComparator);
caseInsensitiveMap.putAll(headers);
this.headers = Collections.unmodifiableMap(caseInsensitiveMap);
}
/**
* Gets the value of a header field for this response.
*
* @param name
* the name of the header field.
* @return the value of the header field, or {@code null} if the header isn't set.
*/
@CheckForNull
public String headerField(String name) {
String result = null;
if (headers.containsKey(name)) {
result = headers.get(name).get(0);
}
return result;
}
/**
* The response body as an {@link InputStream}.
*
* @return the response body
* @throws IOException
* if an I/O Exception occurs.
*/
abstract InputStream bodyStream() throws IOException;
/**
* The error message for this response.
*
* @return if there is an error with some error string, that is returned. If not, {@code null}.
*/
abstract String errorMessage();
/**
* The {@link URL} for this response.
*
* @return the {@link URL} for this response.
*/
@Nonnull
public URL url() {
return request.url();
}
/**
* Gets the {@link GitHubRequest} for this response.
*
* @return the {@link GitHubRequest} for this response.
*/
@Nonnull
public GitHubRequest request() {
return request;
}
/**
* The status code for this response.
*
* @return the status code for this response.
*/
public int statusCode() {
return statusCode;
}
/**
* The headers for this response.
*
* @return the headers for this response.
*/
@Nonnull
public Map<String, List<String>> headers() {
return headers;
}
/**
* Gets the body of the response as a {@link String}.
*
* @return the body of the response as a {@link String}.
* @throws IOException
* if an I/O Exception occurs.
*/
@Nonnull
String getBodyAsString() throws IOException {
InputStreamReader r = null;
r = new InputStreamReader(this.bodyStream(), StandardCharsets.UTF_8);
return IOUtils.toString(r);
}
}
}