Files
github-api/jacoco/org.kohsuke.github/GitHubRequest.java.html
2021-06-02 11:09:28 -07:00

758 lines
29 KiB
HTML

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>GitHubRequest.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">GitHub API for Java</a> &gt; <a href="index.source.html" class="el_package">org.kohsuke.github</a> &gt; <span class="el_source">GitHubRequest.java</span></div><h1>GitHubRequest.java</h1><pre class="source lang-java linenums">package org.kohsuke.github;
import edu.umd.cs.findbugs.annotations.NonNull;
import org.apache.commons.lang3.StringUtils;
import org.kohsuke.github.internal.Previews;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.annotation.WillClose;
import static java.util.Arrays.asList;
/**
* Class {@link GitHubRequest} represents an immutable instance used by the client to determine what information to
* retrieve from a GitHub server. Use the {@link Builder} construct a {@link GitHubRequest}.
* &lt;p&gt;
* NOTE: {@link GitHubRequest} should include the data type to be returned. Any use cases where the same request should
* be used to return different types of data could be handled in some other way. However, the return type is currently
* not specified until late in the building process, so this is still untyped.
* &lt;/p&gt;
*/
class GitHubRequest {
<span class="fc" id="L42"> private static final List&lt;String&gt; METHODS_WITHOUT_BODY = asList(&quot;GET&quot;, &quot;DELETE&quot;);</span>
private final List&lt;Entry&gt; args;
private final Map&lt;String, String&gt; headers;
private final Map&lt;String, Object&gt; injectedMappingValues;
private final String apiUrl;
private final String urlPath;
private final String method;
private final RateLimitTarget rateLimitTarget;
private final InputStream body;
private final boolean forceBody;
private final URL url;
private GitHubRequest(@Nonnull List&lt;Entry&gt; args,
@Nonnull Map&lt;String, String&gt; headers,
@Nonnull Map&lt;String, Object&gt; injectedMappingValues,
@Nonnull String apiUrl,
@Nonnull String urlPath,
@Nonnull String method,
@Nonnull RateLimitTarget rateLimitTarget,
@CheckForNull InputStream body,
<span class="fc" id="L63"> boolean forceBody) throws MalformedURLException {</span>
<span class="fc" id="L64"> this.args = Collections.unmodifiableList(new ArrayList&lt;&gt;(args));</span>
<span class="fc" id="L65"> this.headers = Collections.unmodifiableMap(new LinkedHashMap&lt;&gt;(headers));</span>
<span class="fc" id="L66"> this.injectedMappingValues = Collections.unmodifiableMap(new LinkedHashMap&lt;&gt;(injectedMappingValues));</span>
<span class="fc" id="L67"> this.apiUrl = apiUrl;</span>
<span class="fc" id="L68"> this.urlPath = urlPath;</span>
<span class="fc" id="L69"> this.method = method;</span>
<span class="fc" id="L70"> this.rateLimitTarget = rateLimitTarget;</span>
<span class="fc" id="L71"> this.body = body;</span>
<span class="fc" id="L72"> this.forceBody = forceBody;</span>
<span class="fc" id="L73"> String tailApiUrl = buildTailApiUrl();</span>
<span class="fc" id="L74"> url = getApiURL(apiUrl, tailApiUrl);</span>
<span class="fc" id="L75"> }</span>
/**
* Create a new {@link Builder}.
*
* @return a new {@link Builder}.
*/
public static Builder&lt;?&gt; newBuilder() {
<span class="fc" id="L83"> return new Builder&lt;&gt;();</span>
}
/**
* Gets the final GitHub API URL.
*/
@Nonnull
static URL getApiURL(String apiUrl, String tailApiUrl) throws MalformedURLException {
<span class="fc bfc" id="L91" title="All 2 branches covered."> if (tailApiUrl.startsWith(&quot;/&quot;)) {</span>
<span class="pc bpc" id="L92" title="1 of 2 branches missed."> if (&quot;github.com&quot;.equals(apiUrl)) {// backward compatibility</span>
<span class="nc" id="L93"> return new URL(GitHubClient.GITHUB_URL + tailApiUrl);</span>
} else {
<span class="fc" id="L95"> return new URL(apiUrl + tailApiUrl);</span>
}
} else {
<span class="fc" id="L98"> return new URL(tailApiUrl);</span>
}
}
/**
* Transform Java Enum into Github constants given its conventions
*
* @param en
* Enum to be transformed
* @return a String containing the value of a Github constant
*/
static String transformEnum(Enum&lt;?&gt; en) {
// by convention Java constant names are upper cases, but github uses
// lower-case constants. GitHub also uses '-', which in Java we always
// replace with '_'
<span class="fc" id="L113"> return en.toString().toLowerCase(Locale.ENGLISH).replace('_', '-');</span>
}
/**
* The method for this request, such as &quot;GET&quot;, &quot;PATCH&quot;, or &quot;DELETE&quot;.
*
* @return the request method.
*/
@Nonnull
public String method() {
<span class="fc" id="L123"> return method;</span>
}
/**
* The rate limit target for this request.
*
* @return the rate limit to use for this request.
*/
@Nonnull
public RateLimitTarget rateLimitTarget() {
<span class="fc" id="L133"> return rateLimitTarget;</span>
}
/**
* The arguments for this request. Depending on the {@link #method()} and {@code #inBody()} these maybe added to the
* url or to the request body.
*
* @return the {@link List&lt;Entry&gt;} of arguments
*/
@Nonnull
public List&lt;Entry&gt; args() {
<span class="fc" id="L144"> return args;</span>
}
/**
* The headers for this request.
*
* @return the {@link Map} of headers
*/
@Nonnull
public Map&lt;String, String&gt; headers() {
<span class="fc" id="L154"> return headers;</span>
}
/**
* The headers for this request.
*
* @return the {@link Map} of headers
*/
@Nonnull
public Map&lt;String, Object&gt; injectedMappingValues() {
<span class="fc" id="L164"> return injectedMappingValues;</span>
}
/**
* The base GitHub API URL for this request represented as a {@link String}
*
* @return the url string
*/
@Nonnull
public String apiUrl() {
<span class="nc" id="L174"> return apiUrl;</span>
}
/**
* The url path to be added to the {@link #apiUrl()} for this request. If this does not start with a &quot;/&quot;, it instead
* represents the full url string for this request.
*
* @return a url path or full url string
*/
@Nonnull
public String urlPath() {
<span class="nc" id="L185"> return urlPath;</span>
}
/**
* The content type to to be sent by this request.
*
* @return the content type.
*/
@Nonnull
public String contentType() {
<span class="fc" id="L195"> return headers.get(&quot;Content-type&quot;);</span>
}
/**
* The {@link InputStream} to be sent as the body of this request.
*
* @return the {@link InputStream}.
*/
@CheckForNull
public InputStream body() {
<span class="fc" id="L205"> return body;</span>
}
/**
* The {@link URL} for this request. This is the actual URL the {@link GitHubClient} will send this request to.
*
* @return the request {@link URL}
*/
@Nonnull
public URL url() {
<span class="fc" id="L215"> return url;</span>
}
/**
* Whether arguments for this request should be included in the URL or in the body of the request.
*
* @return true if the arguements should be sent in the body of the request.
*/
public boolean inBody() {
<span class="fc bfc" id="L224" title="All 4 branches covered."> return forceBody || !METHODS_WITHOUT_BODY.contains(method);</span>
}
/**
* Create a {@link Builder} from this request. Initial values of the builder will be the same as this
* {@link GitHubRequest}.
*
* @return a {@link Builder} based on this request.
*/
public Builder&lt;?&gt; toBuilder() {
<span class="fc" id="L234"> return new Builder&lt;&gt;(args,</span>
headers,
injectedMappingValues,
apiUrl,
urlPath,
method,
rateLimitTarget,
body,
forceBody);
}
private String buildTailApiUrl() {
<span class="fc" id="L246"> String tailApiUrl = urlPath;</span>
<span class="fc bfc" id="L247" title="All 6 branches covered."> if (!inBody() &amp;&amp; !args.isEmpty() &amp;&amp; tailApiUrl.startsWith(&quot;/&quot;)) {</span>
try {
<span class="fc" id="L249"> StringBuilder argString = new StringBuilder();</span>
<span class="pc bpc" id="L250" title="1 of 2 branches missed."> boolean questionMarkFound = tailApiUrl.indexOf('?') != -1;</span>
<span class="pc bpc" id="L251" title="1 of 2 branches missed."> argString.append(questionMarkFound ? '&amp;' : '?');</span>
<span class="fc bfc" id="L253" title="All 2 branches covered."> for (Iterator&lt;Entry&gt; it = args.listIterator(); it.hasNext();) {</span>
<span class="fc" id="L254"> Entry arg = it.next();</span>
<span class="fc" id="L255"> argString.append(URLEncoder.encode(arg.key, StandardCharsets.UTF_8.name()));</span>
<span class="fc" id="L256"> argString.append('=');</span>
<span class="fc" id="L257"> argString.append(URLEncoder.encode(arg.value.toString(), StandardCharsets.UTF_8.name()));</span>
<span class="fc bfc" id="L258" title="All 2 branches covered."> if (it.hasNext()) {</span>
<span class="fc" id="L259"> argString.append('&amp;');</span>
}
<span class="fc" id="L261"> }</span>
<span class="fc" id="L262"> tailApiUrl += argString;</span>
<span class="nc" id="L263"> } catch (UnsupportedEncodingException e) {</span>
<span class="nc" id="L264"> throw new GHException(&quot;UTF-8 encoding required&quot;, e);</span>
<span class="fc" id="L265"> }</span>
}
<span class="fc" id="L267"> return tailApiUrl;</span>
}
/**
* Class {@link Builder} follows the builder pattern for {@link GitHubRequest}.
*
* @param &lt;B&gt;
* The type of {@link Builder} to return from the various &quot;with*&quot; methods.
*/
static class Builder&lt;B extends Builder&lt;B&gt;&gt; {
@Nonnull
private final List&lt;Entry&gt; args;
/**
* The header values for this request.
*/
@Nonnull
private final Map&lt;String, String&gt; headers;
/**
* Injected local data map
*/
@Nonnull
private final Map&lt;String, Object&gt; injectedMappingValues;
/**
* The base GitHub API for this request.
*/
@Nonnull
private String apiUrl;
@Nonnull
private String urlPath;
/**
* Request method.
*/
@Nonnull
private String method;
@Nonnull
private RateLimitTarget rateLimitTarget;
private InputStream body;
private boolean forceBody;
/**
* Create a new {@link GitHubRequest.Builder}
*/
protected Builder() {
<span class="fc" id="L317"> this(new ArrayList&lt;&gt;(),</span>
new LinkedHashMap&lt;&gt;(),
new LinkedHashMap&lt;&gt;(),
GitHubClient.GITHUB_URL,
&quot;/&quot;,
&quot;GET&quot;,
RateLimitTarget.CORE,
null,
false);
<span class="fc" id="L326"> }</span>
private Builder(@Nonnull List&lt;Entry&gt; args,
@Nonnull Map&lt;String, String&gt; headers,
@Nonnull Map&lt;String, Object&gt; injectedMappingValues,
@Nonnull String apiUrl,
@Nonnull String urlPath,
@Nonnull String method,
@Nonnull RateLimitTarget rateLimitTarget,
@CheckForNull @WillClose InputStream body,
<span class="fc" id="L336"> boolean forceBody) {</span>
<span class="fc" id="L337"> this.args = new ArrayList&lt;&gt;(args);</span>
<span class="fc" id="L338"> this.headers = new LinkedHashMap&lt;&gt;(headers);</span>
<span class="fc" id="L339"> this.injectedMappingValues = new LinkedHashMap&lt;&gt;(injectedMappingValues);</span>
<span class="fc" id="L340"> this.apiUrl = apiUrl;</span>
<span class="fc" id="L341"> this.urlPath = urlPath;</span>
<span class="fc" id="L342"> this.method = method;</span>
<span class="fc" id="L343"> this.rateLimitTarget = rateLimitTarget;</span>
<span class="fc" id="L344"> this.body = body;</span>
<span class="fc" id="L345"> this.forceBody = forceBody;</span>
<span class="fc" id="L346"> }</span>
/**
* Builds a {@link GitHubRequest} from this builder.
*
* @return a {@link GitHubRequest}
* @throws MalformedURLException
* if the GitHub API URL cannot be constructed
*/
public GitHubRequest build() throws MalformedURLException {
<span class="fc" id="L356"> return new GitHubRequest(args,</span>
headers,
injectedMappingValues,
apiUrl,
urlPath,
method,
rateLimitTarget,
body,
forceBody);
}
/**
* With header requester.
*
* @param url
* the url
* @return the request builder
*/
public B withApiUrl(String url) {
<span class="fc" id="L375"> this.apiUrl = url;</span>
<span class="fc" id="L376"> return (B) this;</span>
}
/**
* Sets the request HTTP header.
* &lt;p&gt;
* If a header of the same name is already set, this method overrides it.
*
* @param name
* the name
* @param value
* the value
* @return the request builder
*/
public B setHeader(String name, String value) {
<span class="fc" id="L391"> headers.put(name, value);</span>
<span class="fc" id="L392"> return (B) this;</span>
}
/**
* With header requester.
*
* @param name
* the name
* @param value
* the value
* @return the request builder
*/
public B withHeader(String name, String value) {
<span class="fc" id="L405"> String oldValue = headers.get(name);</span>
<span class="fc bfc" id="L406" title="All 2 branches covered."> if (!StringUtils.isBlank(oldValue)) {</span>
<span class="fc" id="L407"> value = oldValue + &quot;, &quot; + value;</span>
}
<span class="fc" id="L409"> return setHeader(name, value);</span>
}
/**
* Object to inject into binding.
*
* @param value
* the value
* @return the request builder
*/
public B injectMappingValue(@NonNull Object value) {
<span class="fc" id="L420"> return injectMappingValue(value.getClass().getName(), value);</span>
}
/**
* Object to inject into binding.
*
* @param name
* the name
* @param value
* the value
* @return the request builder
*/
public B injectMappingValue(@NonNull String name, Object value) {
<span class="fc" id="L433"> this.injectedMappingValues.put(name, value);</span>
<span class="fc" id="L434"> return (B) this;</span>
}
public B withPreview(String name) {
<span class="fc" id="L438"> return withHeader(&quot;Accept&quot;, name);</span>
}
public B withPreview(Previews preview) {
<span class="fc" id="L442"> return withPreview(preview.mediaType());</span>
}
/**
* With requester.
*
* @param Map
* map of key value pairs to add
* @return the request builder
*/
public B with(Map&lt;String, Object&gt; map) {
<span class="nc bnc" id="L453" title="All 2 branches missed."> for (Map.Entry&lt;String, Object&gt; entry : map.entrySet()) {</span>
<span class="nc" id="L454"> with(entry.getKey(), entry.getValue());</span>
<span class="nc" id="L455"> }</span>
<span class="nc" id="L457"> return (B) this;</span>
}
/**
* With requester.
*
* @param key
* the key
* @param value
* the value
* @return the request builder
*/
public B with(String key, int value) {
<span class="fc" id="L470"> return with(key, (Object) value);</span>
}
/**
* With requester.
*
* @param key
* the key
* @param value
* the value
* @return the request builder
*/
public B with(String key, long value) {
<span class="fc" id="L483"> return with(key, (Object) value);</span>
}
/**
* With requester.
*
* @param key
* the key
* @param value
* the value
* @return the request builder
*/
public B with(String key, boolean value) {
<span class="fc" id="L496"> return with(key, (Object) value);</span>
}
/**
* With requester.
*
* @param key
* the key
* @param e
* the e
* @return the request builder
*/
public B with(String key, Enum&lt;?&gt; e) {
<span class="fc bfc" id="L509" title="All 2 branches covered."> if (e == null)</span>
<span class="fc" id="L510"> return with(key, (Object) null);</span>
<span class="fc" id="L511"> return with(key, transformEnum(e));</span>
}
/**
* With requester.
*
* @param key
* the key
* @param value
* the value
* @return the request builder
*/
public B with(String key, String value) {
<span class="fc" id="L524"> return with(key, (Object) value);</span>
}
/**
* With requester.
*
* @param key
* the key
* @param value
* the value
* @return the request builder
*/
public B with(String key, Collection&lt;?&gt; value) {
<span class="fc" id="L537"> return with(key, (Object) value);</span>
}
/**
* With requester.
*
* @param key
* the key
* @param value
* the value
* @return the request builder
*/
public B with(String key, Map&lt;?, ?&gt; value) {
<span class="fc" id="L550"> return with(key, (Object) value);</span>
}
/**
* With requester.
*
* @param body
* the body
* @return the request builder
*/
public B with(@WillClose /* later */ InputStream body) {
<span class="fc" id="L561"> this.body = body;</span>
<span class="fc" id="L562"> return (B) this;</span>
}
/**
* With nullable requester.
*
* @param key
* the key
* @param value
* the value
* @return the request builder
*/
public B withNullable(String key, Object value) {
<span class="fc" id="L575"> args.add(new Entry(key, value));</span>
<span class="fc" id="L576"> return (B) this;</span>
}
/**
* With requester.
*
* @param key
* the key
* @param value
* the value
* @return the request builder
*/
public B with(String key, Object value) {
<span class="fc bfc" id="L589" title="All 2 branches covered."> if (value != null) {</span>
<span class="fc" id="L590"> args.add(new Entry(key, value));</span>
}
<span class="fc" id="L592"> return (B) this;</span>
}
/**
* Unlike {@link #with(String, String)}, overrides the existing value
*
* @param key
* the key
* @param value
* the value
* @return the request builder
*/
public B set(String key, Object value) {
<span class="fc" id="L605"> remove(key);</span>
<span class="fc" id="L606"> return with(key, value);</span>
}
/**
* Removes all arg entries for a specific key.
*
* @param key
* the key
* @return the request builder
*/
public B remove(String key) {
<span class="fc bfc" id="L618" title="All 2 branches covered."> for (int index = 0; index &lt; args.size();) {</span>
<span class="fc bfc" id="L619" title="All 2 branches covered."> if (args.get(index).key.equals(key)) {</span>
<span class="fc" id="L620"> args.remove(index);</span>
} else {
<span class="fc" id="L622"> index++;</span>
}
}
<span class="fc" id="L625"> return (B) this;</span>
}
/**
* Method requester.
*
* @param method
* the method
* @return the request builder
*/
public B method(@Nonnull String method) {
<span class="fc" id="L636"> this.method = method;</span>
<span class="fc" id="L637"> return (B) this;</span>
}
/**
* Method requester.
*
* @param rateLimitTarget
* the rate limit target for this request. Default is {@link RateLimitTarget#CORE}.
* @return the request builder
*/
public B rateLimit(@Nonnull RateLimitTarget rateLimitTarget) {
<span class="fc" id="L648"> this.rateLimitTarget = rateLimitTarget;</span>
<span class="fc" id="L649"> return (B) this;</span>
}
/**
* Content type requester.
*
* @param contentType
* the content type
* @return the request builder
*/
public B contentType(String contentType) {
<span class="fc" id="L660"> this.headers.put(&quot;Content-type&quot;, contentType);</span>
<span class="fc" id="L661"> return (B) this;</span>
}
/**
* NOT FOR PUBLIC USE. Do not make this method public.
* &lt;p&gt;
* Sets the path component of api URL without URI encoding.
* &lt;p&gt;
* Should only be used when passing a literal URL field from a GHObject, such as {@link GHContent#refresh()} or
* when needing to set query parameters on requests methods that don't usually have them, such as
* {@link GHRelease#uploadAsset(String, InputStream, String)}.
*
* @param rawUrlPath
* the content type
* @return the request builder
*/
B setRawUrlPath(@Nonnull String rawUrlPath) {
<span class="fc" id="L678"> Objects.requireNonNull(rawUrlPath);</span>
// This method should only work for full urls, which must start with &quot;http&quot;
<span class="fc bfc" id="L680" title="All 2 branches covered."> if (!rawUrlPath.startsWith(&quot;http&quot;)) {</span>
<span class="fc" id="L681"> throw new GHException(&quot;Raw URL must start with 'http'&quot;);</span>
}
<span class="fc" id="L683"> this.urlPath = rawUrlPath;</span>
<span class="fc" id="L684"> return (B) this;</span>
}
/**
* Path component of api URL. Appended to api url.
* &lt;p&gt;
* If urlPath starts with a slash, it will be URI encoded as a path. If it starts with anything else, it will be
* used as is.
*
* @param urlPathItems
* the content type
* @return the request builder
*/
public B withUrlPath(@Nonnull String urlPath, @Nonnull String... urlPathItems) {
// full url may be set and reset as needed
<span class="fc bfc" id="L699" title="All 4 branches covered."> if (urlPathItems.length == 0 &amp;&amp; !urlPath.startsWith(&quot;/&quot;)) {</span>
<span class="fc" id="L700"> return setRawUrlPath(urlPath);</span>
}
// Once full url is set, do not allow path setting
<span class="pc bpc" id="L704" title="1 of 2 branches missed."> if (!this.urlPath.startsWith(&quot;/&quot;)) {</span>
<span class="nc" id="L705"> throw new GHException(&quot;Cannot append to url path after setting a full url&quot;);</span>
}
<span class="fc" id="L708"> String tailUrlPath = urlPath;</span>
<span class="fc bfc" id="L709" title="All 2 branches covered."> if (urlPathItems.length != 0) {</span>
<span class="fc" id="L710"> tailUrlPath += &quot;/&quot; + String.join(&quot;/&quot;, urlPathItems);</span>
}
<span class="fc" id="L713"> tailUrlPath = StringUtils.prependIfMissing(tailUrlPath, &quot;/&quot;);</span>
<span class="fc" id="L715"> this.urlPath = urlPathEncode(tailUrlPath);</span>
<span class="fc" id="L716"> return (B) this;</span>
}
/**
* Small number of GitHub APIs use HTTP methods somewhat inconsistently, and use a body where it's not expected.
* Normally whether parameters go as query parameters or a body depends on the HTTP verb in use, but this method
* forces the parameters to be sent as a body.
*
* @return the request builder
*/
public B inBody() {
<span class="fc" id="L727"> forceBody = true;</span>
<span class="fc" id="L728"> return (B) this;</span>
}
}
protected static class Entry {
final String key;
final Object value;
<span class="fc" id="L736"> protected Entry(String key, Object value) {</span>
<span class="fc" id="L737"> this.key = key;</span>
<span class="fc" id="L738"> this.value = value;</span>
<span class="fc" id="L739"> }</span>
}
/**
* Encode the path to url safe string.
*
* @param value
* string to be path encoded.
* @return The encoded string.
*/
private static String urlPathEncode(String value) {
try {
<span class="fc" id="L751"> return new URI(null, null, value, null, null).toASCIIString();</span>
<span class="nc" id="L752"> } catch (URISyntaxException ex) {</span>
<span class="nc" id="L753"> throw new AssertionError(ex);</span>
}
}
}
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.7.202105040129</span></div></body></html>