headers = new ArrayList<>(request.headers().get(headerKey));
+ headers.add(headerValue);
+ builder.putHeaders(headerKey, headers);
+ } else {
+ builder.putHeaders(headerKey, List.of(headerValue));
+ }
+ });
+ return builder;
+ }
+
+ private ImmutableHttpRequest.Builder requestBuilder(final String path) {
+
+ return ImmutableHttpRequest.builder()
+ .url(urlFor(path))
+ .method("GET")
+ .body("")
+ .putHeaders(HttpHeaders.ACCEPT, List.of(MediaType.APPLICATION_JSON))
+ .putHeaders(HttpHeaders.CONTENT_TYPE, List.of(MediaType.APPLICATION_JSON))
+ .putHeaders(HttpHeaders.AUTHORIZATION, List.of(getAuthorizationHeader(path)));
+ }
+
+ private ImmutableHttpRequest.Builder graphqlRequestBuilder() {
+ URI url = graphqlUrl.orElseThrow(() -> new IllegalStateException("No graphql url set"));
+ return ImmutableHttpRequest.builder()
+ .url(url.toString())
+ .putHeaders(HttpHeaders.ACCEPT, List.of(MediaType.APPLICATION_JSON))
+ .putHeaders(HttpHeaders.CONTENT_TYPE, List.of(MediaType.APPLICATION_JSON))
+ .putHeaders(HttpHeaders.AUTHORIZATION, List.of(getAuthorizationHeader("/graphql")));
+ }
+
+ public boolean isGraphqlEnabled() {
+ return graphqlUrl.isPresent();
+ }
+
+ /*
+ Generates the Authentication header, given the API endpoint and the credentials provided.
+
+ GitHub Requests can be authenticated in 3 different ways.
+ (1) Regular, static access token;
+ (2) JWT Token, generated from a private key. Used in GitHub Apps;
+ (3) Installation Token, generated from the JWT token. Also used in GitHub Apps.
+ */
+ private String getAuthorizationHeader(final String path) {
+ if (isJwtRequest(path) && getPrivateKey().isEmpty()) {
+ throw new IllegalStateException("This endpoint needs a client with a private key for an App");
}
-
- /**
- * Make an http GET request for the given path on the server
- *
- * @param path relative to the Github base url
- * @param extraHeaders extra github headers to be added to the call
- * @return body deserialized as provided type
- */
- CompletableFuture request(
- final String path, final Class clazz, final Map extraHeaders) {
- final Request.Builder builder = requestBuilder(path);
- extraHeaders.forEach(builder::addHeader);
- final Request request = builder.build();
- log.debug("Making request to {}", request.url().toString());
- return call(request)
- .thenApply(body -> json().fromJsonUncheckedNotNull(responseBodyUnchecked(body), clazz));
+ if (getAccessToken().isPresent()) {
+ return String.format("token %s", token);
+ } else if (getPrivateKey().isPresent()) {
+ final String jwtToken;
+ try {
+ jwtToken = JwtTokenIssuer.fromPrivateKey(privateKey).getToken(appId);
+ } catch (Exception e) {
+ throw new RuntimeException("There was an error generating JWT token", e);
+ }
+ if (isJwtRequest(path)) {
+ return String.format("Bearer %s", jwtToken);
+ }
+ if (installationId == null) {
+ throw new RuntimeException("This endpoint needs a client with an installation ID");
+ }
+ try {
+ return String.format("token %s", getInstallationToken(jwtToken, installationId));
+ } catch (Exception e) {
+ throw new RuntimeException("Could not generate access token for github app", e);
+ }
}
+ throw new RuntimeException("Not possible to authenticate. ");
+ }
- /**
- * Make an http request for the given path on the Github server.
- *
- * @param path relative to the Github base url
- * @param extraHeaders extra github headers to be added to the call
- * @return body deserialized as provided type
- */
- CompletableFuture request(
- final String path,
- final TypeReference typeReference,
- final Map extraHeaders) {
- final Request.Builder builder = requestBuilder(path);
- extraHeaders.forEach(builder::addHeader);
- final Request request = builder.build();
- log.debug("Making request to {}", request.url().toString());
- return call(request)
- .thenApply(
- response ->
- json().fromJsonUncheckedNotNull(responseBodyUnchecked(response), typeReference));
- }
+ private boolean isJwtRequest(final String path) {
+ return path.startsWith("/app/installation") || path.endsWith("installation");
+ }
- /**
- * Make an http request for the given path on the Github server.
- *
- * @param path relative to the Github base url
- * @return body deserialized as provided type
- */
- CompletableFuture request(final String path, final TypeReference typeReference) {
- final Request request = requestBuilder(path).build();
- log.debug("Making request to {}", request.url().toString());
- return call(request)
- .thenApply(
- response ->
- json().fromJsonUncheckedNotNull(responseBodyUnchecked(response), typeReference));
- }
+ private String getInstallationToken(final String jwtToken, final int installationId)
+ throws Exception {
- /**
- * Make an http POST request for the given path with provided JSON body.
- *
- * @param path relative to the Github base url
- * @param data request body as stringified JSON
- * @return response body as String
- */
- CompletableFuture post(final String path, final String data) {
- final Request request =
- requestBuilder(path)
- .method("POST", RequestBody.create(parse(MediaType.APPLICATION_JSON), data))
- .build();
- log.debug("Making POST request to {}", request.url().toString());
- return call(request);
- }
+ AccessToken installationToken = installationTokens.get(installationId);
- /**
- * Make an http POST request for the given path with provided JSON body.
- *
- * @param path relative to the Github base url
- * @param data request body as stringified JSON
- * @param extraHeaders
- * @return response body as String
- */
- CompletableFuture post(
- final String path, final String data, final Map extraHeaders) {
- final Request.Builder builder =
- requestBuilder(path)
- .method("POST", RequestBody.create(parse(MediaType.APPLICATION_JSON), data));
- extraHeaders.forEach(builder::addHeader);
- final Request request = builder.build();
- log.debug("Making POST request to {}", request.url().toString());
- return call(request);
+ if (installationToken == null || isExpired(installationToken)) {
+ log.info(
+ "Github token for installation {} is either expired or null. Trying to get a new one.",
+ installationId);
+ installationToken = generateInstallationToken(jwtToken, installationId);
+ installationTokens.put(installationId, installationToken);
}
-
- /**
- * Make an http POST request for the given path with provided JSON body.
- *
- * @param path relative to the Github base url
- * @param data request body as stringified JSON
- * @param clazz class to cast response as
- * @param extraHeaders
- * @return response body deserialized as provided class
- */
- CompletableFuture post(
- final String path,
- final String data,
- final Class clazz,
- final Map extraHeaders) {
- return post(path, data, extraHeaders)
- .thenApply(
- response -> json().fromJsonUncheckedNotNull(responseBodyUnchecked(response), clazz));
- }
-
- /**
- * Make an http POST request for the given path with provided JSON body.
- *
- * @param path relative to the Github base url
- * @param data request body as stringified JSON
- * @param clazz class to cast response as
- * @return response body deserialized as provided class
- */
- CompletableFuture post(final String path, final String data, final Class clazz) {
- return post(path, data)
- .thenApply(
- response -> json().fromJsonUncheckedNotNull(responseBodyUnchecked(response), clazz));
+ return installationToken.token();
+ }
+
+ private boolean isExpired(final AccessToken token) {
+ // Adds a few minutes to avoid making calls with an expired token due to clock differences
+ return token.expiresAt().isBefore(ZonedDateTime.now().plusMinutes(EXPIRY_MARGIN_IN_MINUTES));
+ }
+
+ private AccessToken generateInstallationToken(final String jwtToken, final int installationId)
+ throws Exception {
+ log.info("Got JWT Token. Now getting Github access_token for installation {}", installationId);
+ final String url = String.format(urlFor(GET_ACCESS_TOKEN_URL), installationId);
+ final HttpRequest request =
+ ImmutableHttpRequest.builder()
+ .url(url)
+ .putHeaders("Accept", List.of("application/vnd.github.machine-man-preview+json"))
+ .putHeaders("Authorization", List.of("Bearer " + jwtToken))
+ .method("POST")
+ .body("")
+ .build();
+
+ final HttpResponse response = this.client.send(request).toCompletableFuture().join();
+
+ if (!response.isSuccessful()) {
+ throw new Exception(
+ String.format(
+ "Got non-2xx status %s when getting an access token from GitHub: %s",
+ response.statusCode(), response.statusMessage()));
}
- /**
- * Make a POST request to the graphql endpoint of Github
- *
- * @param data request body as stringified JSON
- * @return response
- * @see "https://docs.github.com/en/enterprise-server@3.9/graphql/guides/forming-calls-with-graphql#communicating-with-graphql"
- */
- public CompletableFuture postGraphql(final String data) {
- final Request request =
- graphqlRequestBuilder()
- .method("POST", RequestBody.create(parse(MediaType.APPLICATION_JSON), data))
- .build();
- log.info("Making POST request to {}", request.url());
- return call(request);
+ if (response.bodyString() == null) {
+ throw new Exception(
+ String.format(
+ "Got empty response body when getting an access token from GitHub, HTTP status was: %s",
+ response.statusMessage()));
}
-
- /**
- * Make an http PUT request for the given path with provided JSON body.
- *
- * @param path relative to the Github base url
- * @param data request body as stringified JSON
- * @return response body as String
- */
- CompletableFuture put(final String path, final String data) {
- final Request request =
- requestBuilder(path)
- .method("PUT", RequestBody.create(parse(MediaType.APPLICATION_JSON), data))
- .build();
- log.debug("Making POST request to {}", request.url().toString());
- return call(request);
- }
-
- /**
- * Make a HTTP PUT request for the given path with provided JSON body.
- *
- * @param path relative to the Github base url
- * @param data request body as stringified JSON
- * @param clazz class to cast response as
- * @return response body deserialized as provided class
- */
- CompletableFuture put(final String path, final String data, final Class clazz) {
- return put(path, data)
- .thenApply(
- response -> json().fromJsonUncheckedNotNull(responseBodyUnchecked(response), clazz));
- }
-
- /**
- * Make an http PATCH request for the given path with provided JSON body.
- *
- * @param path relative to the Github base url
- * @param data request body as stringified JSON
- * @return response body as String
- */
- CompletableFuture patch(final String path, final String data) {
- final Request request =
- requestBuilder(path)
- .method("PATCH", RequestBody.create(parse(MediaType.APPLICATION_JSON), data))
- .build();
- log.debug("Making PATCH request to {}", request.url().toString());
- return call(request);
- }
-
- /**
- * Make an http PATCH request for the given path with provided JSON body.
- *
- * @param path relative to the Github base url
- * @param data request body as stringified JSON
- * @param clazz class to cast response as
- * @return response body deserialized as provided class
- */
- CompletableFuture patch(final String path, final String data, final Class clazz) {
- return patch(path, data)
- .thenApply(
- response -> json().fromJsonUncheckedNotNull(responseBodyUnchecked(response), clazz));
- }
-
- /**
- * Make an http PATCH request for the given path with provided JSON body
- *
- * @param path relative to the Github base url
- * @param data request body as stringified JSON
- * @param clazz class to cast response as
- * @return response body deserialized as provided class
- */
- CompletableFuture patch(
- final String path,
- final String data,
- final Class clazz,
- final Map extraHeaders) {
- final Request.Builder builder =
- requestBuilder(path)
- .method("PATCH", RequestBody.create(parse(MediaType.APPLICATION_JSON), data));
- extraHeaders.forEach(builder::addHeader);
- final Request request = builder.build();
- log.debug("Making PATCH request to {}", request.url().toString());
- return call(request)
- .thenApply(
- response -> json().fromJsonUncheckedNotNull(responseBodyUnchecked(response), clazz));
- }
-
- /**
- * Make an http DELETE request for the given path.
- *
- * @param path relative to the Github base url
- * @return response body as String
- */
- CompletableFuture delete(final String path) {
- final Request request = requestBuilder(path).delete().build();
- log.debug("Making DELETE request to {}", request.url().toString());
- return call(request);
- }
-
- /**
- * Make an http DELETE request for the given path.
- *
- * @param path relative to the Github base url
- * @param data request body as stringified JSON
- * @return response body as String
- */
- CompletableFuture delete(final String path, final String data) {
- final Request request =
- requestBuilder(path)
- .method("DELETE", RequestBody.create(parse(MediaType.APPLICATION_JSON), data))
- .build();
- log.debug("Making DELETE request to {}", request.url().toString());
- return call(request);
- }
-
- /**
- * Create a URL for a given path to this Github server.
- *
- * @param path relative URI
- * @return URL to path on this server
- */
- String urlFor(final String path) {
- return baseUrl.toString().replaceAll("/+$", "") + "/" + path.replaceAll("^/+", "");
- }
-
- private Request.Builder requestBuilder(final String path) {
- final Request.Builder builder =
- new Request.Builder()
- .url(urlFor(path))
- .addHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON)
- .addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
- builder.addHeader(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(path));
-
- return builder;
+ final String text = response.bodyString();
+ return Json.create().fromJson(text, AccessToken.class);
+ }
+
+ private CompletableFuture call(final HttpRequest httpRequest) {
+ return this.client
+ .send(httpRequest)
+ .thenCompose(httpResponse -> handleResponse(httpRequest, httpResponse));
+ }
+
+ private CompletableFuture handleResponse(
+ final HttpRequest httpRequest, final HttpResponse httpResponse) {
+ final CompletableFuture future = new CompletableFuture<>();
+ // avoid multiple redirects
+ final AtomicBoolean redirected = new AtomicBoolean(false);
+ processPossibleRedirects(httpResponse, redirected)
+ .handle(
+ (res, ex) -> {
+ if (Objects.nonNull(ex)) {
+ future.completeExceptionally(ex);
+ } else if (!res.isSuccessful()) {
+ try {
+ future.completeExceptionally(mapException(httpRequest, res));
+ } catch (final Throwable e) {
+ future.completeExceptionally(e);
+ }
+ } else {
+ future.complete(res);
+ }
+ return res;
+ })
+ .join();
+ return future;
+ }
+
+ private RequestNotOkException mapException(
+ final HttpRequest httpRequest, final HttpResponse httpResponse) throws IOException {
+ String bodyString = Optional.ofNullable(httpResponse.bodyString()).orElse("");
+ Map> headersMap = httpResponse.headers();
+
+ if (httpResponse.statusCode() == FORBIDDEN) {
+ if (bodyString.contains("Repository was archived so is read-only")) {
+ return new ReadOnlyRepositoryException(
+ httpRequest.method(),
+ URI.create(httpRequest.url()).getPath(),
+ httpResponse.statusCode(),
+ bodyString,
+ headersMap);
+ }
}
- private Request.Builder graphqlRequestBuilder() {
- URI url = graphqlUrl.orElseThrow(() -> new IllegalStateException("No graphql url set"));
- final Request.Builder builder =
- new Request.Builder()
- .url(url.toString())
- .addHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON)
- .addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
- builder.addHeader(HttpHeaders.AUTHORIZATION, getAuthorizationHeader("/graphql"));
- return builder;
+ return new RequestNotOkException(
+ httpRequest.method(),
+ URI.create(httpRequest.url()).getPath(),
+ httpResponse.statusCode(),
+ bodyString,
+ headersMap);
+ }
+
+ CompletableFuture processPossibleRedirects(
+ final HttpResponse response, final AtomicBoolean redirected) {
+ if (response.statusCode() >= PERMANENT_REDIRECT
+ && response.statusCode() <= TEMPORARY_REDIRECT
+ && !redirected.get()) {
+ redirected.set(true);
+ // redo the same request with a new URL
+ final String newLocation = response.headers().get("Location").get(0);
+ final HttpRequest request =
+ requestBuilder(newLocation)
+ .url(newLocation)
+ .method(response.request().method())
+ .body(response.request().body())
+ .build();
+ // Do the new call and complete the original future when the new call completes
+ return call(request);
}
- public boolean isGraphqlEnabled() {
- return graphqlUrl.isPresent();
+ return completedFuture(response);
+ }
+
+ /** Wrapper to Constructors that expose File object for the privateKey argument */
+ private static GitHubClient createOrThrow(
+ final OkHttpClient httpClient,
+ final URI baseUrl,
+ final URI graphqlUrl,
+ final File privateKey,
+ final Integer appId,
+ final Integer installationId) {
+ try {
+ return new GitHubClient(
+ httpClient,
+ baseUrl,
+ graphqlUrl,
+ null,
+ FileUtils.readFileToByteArray(privateKey),
+ appId,
+ installationId);
+ } catch (IOException e) {
+ throw new RuntimeException("There was an error generating JWT token", e);
}
-
-
- /*
- Generates the Authentication header, given the API endpoint and the credentials provided.
-
- Github Requests can be authenticated in 3 different ways.
- (1) Regular, static access token;
- (2) JWT Token, generated from a private key. Used in Github Apps;
- (3) Installation Token, generated from the JWT token. Also used in Github Apps.
- */
- private String getAuthorizationHeader(final String path) {
- if (isJwtRequest(path) && getPrivateKey().isEmpty()) {
- throw new IllegalStateException("This endpoint needs a client with a private key for an App");
- }
- if (getAccessToken().isPresent()) {
- return String.format("token %s", token);
- } else if (getPrivateKey().isPresent()) {
- final String jwtToken;
- try {
- jwtToken = JwtTokenIssuer.fromPrivateKey(privateKey).getToken(appId);
- } catch (Exception e) {
- throw new RuntimeException("There was an error generating JWT token", e);
- }
- if (isJwtRequest(path)) {
- return String.format("Bearer %s", jwtToken);
- }
- if (installationId == null) {
- throw new RuntimeException("This endpoint needs a client with an installation ID");
- }
- try {
- return String.format("token %s", getInstallationToken(jwtToken, installationId));
- } catch (Exception e) {
- throw new RuntimeException("Could not generate access token for github app", e);
- }
- }
- throw new RuntimeException("Not possible to authenticate. ");
- }
-
- private boolean isJwtRequest(final String path) {
- return path.startsWith("/app/installation") || path.endsWith("installation");
- }
-
- private String getInstallationToken(final String jwtToken, final int installationId)
- throws Exception {
-
- AccessToken installationToken = installationTokens.get(installationId);
-
- if (installationToken == null || isExpired(installationToken)) {
- log.info(
- "Github token for installation {} is either expired or null. Trying to get a new one.",
- installationId);
- installationToken = generateInstallationToken(jwtToken, installationId);
- installationTokens.put(installationId, installationToken);
- }
- return installationToken.token();
- }
-
- private boolean isExpired(final AccessToken token) {
- // Adds a few minutes to avoid making calls with an expired token due to clock differences
- return token.expiresAt().isBefore(ZonedDateTime.now().plusMinutes(EXPIRY_MARGIN_IN_MINUTES));
- }
-
- private AccessToken generateInstallationToken(final String jwtToken, final int installationId)
- throws Exception {
- log.info("Got JWT Token. Now getting Github access_token for installation {}", installationId);
- final String url = String.format(urlFor(GET_ACCESS_TOKEN_URL), installationId);
- final Request request =
- new Request.Builder()
- .addHeader("Accept", "application/vnd.github.machine-man-preview+json")
- .addHeader("Authorization", "Bearer " + jwtToken)
- .url(url)
- .method("POST", RequestBody.create(parse(MediaType.APPLICATION_JSON), ""))
- .build();
-
- final Response response = client.newCall(request).execute();
-
- if (!response.isSuccessful()) {
- throw new Exception(
- String.format(
- "Got non-2xx status %s when getting an access token from GitHub: %s",
- response.code(), response.message()));
- }
-
- if (response.body() == null) {
- throw new Exception(
- String.format(
- "Got empty response body when getting an access token from GitHub, HTTP status was: %s",
- response.message()));
- }
- final String text = response.body().string();
- response.body().close();
- return Json.create().fromJson(text, AccessToken.class);
- }
-
- private CompletableFuture call(final Request request) {
- try (Span span = tracer.span(request)) {
- if (this.callFactory == null) {
- this.callFactory = this.tracer.createTracedClient(this.client);
- }
- final Call call = this.callFactory.newCall(request);
-
- final CompletableFuture future = new CompletableFuture<>();
-
- // avoid multiple redirects
- final AtomicBoolean redirected = new AtomicBoolean(false);
-
- call.enqueue(
- new Callback() {
- @Override
- public void onFailure(@NotNull final Call call, final IOException e) {
- future.completeExceptionally(e);
- }
-
- @Override
- public void onResponse(@NotNull final Call call, final Response response) {
- processPossibleRedirects(response, redirected)
- .handle(
- (res, ex) -> {
- if (Objects.nonNull(ex)) {
- future.completeExceptionally(ex);
- } else if (!res.isSuccessful()) {
- try {
- future.completeExceptionally(mapException(res, request));
- } catch (final Throwable e) {
- future.completeExceptionally(e);
- } finally {
- if (res.body() != null) {
- res.body().close();
- }
- }
- } else {
- future.complete(res);
- }
- return res;
- });
- }
- });
- tracer.attachSpanToFuture(span, future);
- return future;
- }
- }
-
- private RequestNotOkException mapException(final Response res, final Request request)
- throws IOException {
- String bodyString = res.body() != null ? res.body().string() : "";
- Map> headersMap = res.headers().toMultimap();
-
- if (res.code() == FORBIDDEN) {
- if (bodyString.contains("Repository was archived so is read-only")) {
- return new ReadOnlyRepositoryException(request.method(), request.url().encodedPath(), res.code(), bodyString, headersMap);
- }
- }
-
- return new RequestNotOkException(request.method(), request.url().encodedPath(), res.code(), bodyString, headersMap);
- }
-
- CompletableFuture processPossibleRedirects(
- final Response response, final AtomicBoolean redirected) {
- if (response.code() >= PERMANENT_REDIRECT
- && response.code() <= TEMPORARY_REDIRECT
- && !redirected.get()) {
- redirected.set(true);
- // redo the same request with a new URL
- final String newLocation = response.header("Location");
- final Request request =
- requestBuilder(newLocation)
- .url(newLocation)
- .method(response.request().method(), response.request().body())
- .build();
- // Do the new call and complete the original future when the new call completes
- return call(request);
- }
-
- return completedFuture(response);
- }
-
- /**
- * Wrapper to Constructors that expose File object for the privateKey argument
- */
- private static GitHubClient createOrThrow(final OkHttpClient httpClient, final URI baseUrl, final URI graphqlUrl, final File privateKey, final Integer appId, final Integer installationId) {
- try {
- return new GitHubClient(httpClient, baseUrl, graphqlUrl, null, FileUtils.readFileToByteArray(privateKey), appId, installationId);
- } catch (IOException e) {
- throw new RuntimeException("There was an error generating JWT token", e);
- }
+ }
+
+ /** Wrapper to Constructors that expose File object for the privateKey argument */
+ private static GitHubClient createOrThrow(
+ final HttpClient httpClient,
+ final URI baseUrl,
+ final URI graphqlUrl,
+ final File privateKey,
+ final Integer appId,
+ final Integer installationId) {
+ try {
+ return new GitHubClient(
+ httpClient,
+ baseUrl,
+ graphqlUrl,
+ null,
+ FileUtils.readFileToByteArray(privateKey),
+ appId,
+ installationId);
+ } catch (IOException e) {
+ throw new RuntimeException("There was an error generating JWT token", e);
}
+ }
}
diff --git a/src/main/java/com/spotify/github/v3/clients/GithubPage.java b/src/main/java/com/spotify/github/v3/clients/GithubPage.java
index 6bbd4ad0..ee3f4436 100644
--- a/src/main/java/com/spotify/github/v3/clients/GithubPage.java
+++ b/src/main/java/com/spotify/github/v3/clients/GithubPage.java
@@ -7,9 +7,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -20,7 +20,6 @@
package com.spotify.github.v3.clients;
-import static com.spotify.github.v3.clients.GitHubClient.responseBodyUnchecked;
import static java.util.Arrays.stream;
import static java.util.Objects.nonNull;
import static java.util.function.Function.identity;
@@ -37,8 +36,6 @@
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
-import java.util.stream.Stream;
-import okhttp3.ResponseBody;
/**
* Async page implementation for github resources
@@ -147,9 +144,7 @@ public Iterator iterator() {
.request(path)
.thenApply(
response ->
- github
- .json()
- .fromJsonUncheckedNotNull(responseBodyUnchecked(response), typeReference))
+ github.json().fromJsonUncheckedNotNull(response.bodyString(), typeReference))
.join()
.iterator();
}
@@ -159,13 +154,12 @@ private CompletableFuture