Skip to content

Commit 0c10654

Browse files
committed
first pass getting paging thru results working
1 parent e56aecc commit 0c10654

File tree

11 files changed

+485
-26
lines changed

11 files changed

+485
-26
lines changed

src/main/java/org/sourcelab/buildkite/api/client/BuildkiteClient.java

Lines changed: 164 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.sourcelab.buildkite.api.client.exception.BuildkiteException;
2121
import org.sourcelab.buildkite.api.client.exception.InvalidAccessTokenException;
2222
import org.sourcelab.buildkite.api.client.exception.InvalidAllowedIpAddressException;
23+
import org.sourcelab.buildkite.api.client.exception.InvalidPagingRequestException;
2324
import org.sourcelab.buildkite.api.client.exception.NotFoundException;
2425
import org.sourcelab.buildkite.api.client.http.Client;
2526
import org.sourcelab.buildkite.api.client.http.HttpResult;
@@ -31,6 +32,8 @@
3132
import org.sourcelab.buildkite.api.client.request.GetUserRequest;
3233
import org.sourcelab.buildkite.api.client.request.ListBuildsRequest;
3334
import org.sourcelab.buildkite.api.client.request.ListEmojisRequest;
35+
import org.sourcelab.buildkite.api.client.request.PageOptions;
36+
import org.sourcelab.buildkite.api.client.request.PageableRequest;
3437
import org.sourcelab.buildkite.api.client.request.PingRequest;
3538
import org.sourcelab.buildkite.api.client.request.Request;
3639
import org.sourcelab.buildkite.api.client.response.AccessTokenResponse;
@@ -39,11 +42,14 @@
3942
import org.sourcelab.buildkite.api.client.response.ErrorResponse;
4043
import org.sourcelab.buildkite.api.client.response.ListBuildsResponse;
4144
import org.sourcelab.buildkite.api.client.response.MetaResponse;
45+
import org.sourcelab.buildkite.api.client.response.PageableResponse;
46+
import org.sourcelab.buildkite.api.client.response.PagingLinks;
4247
import org.sourcelab.buildkite.api.client.response.PingResponse;
4348
import org.sourcelab.buildkite.api.client.response.parser.ErrorResponseParser;
4449

4550
import java.io.IOException;
4651
import java.util.List;
52+
import java.util.Objects;
4753

4854
/**
4955
* API Client for Buildkite's REST Api.
@@ -121,23 +127,180 @@ public MetaResponse getMeta() throws BuildkiteException {
121127
* List all of the Emojis defined in the given Organization.
122128
* @param orgIdSlug Organization Id slug to retrieve list of emojis for.
123129
* @return List of Emojis.
130+
* @throws BuildkiteException if API returns an error response.
124131
*/
125132
public List<Emoji> listEmojis(final String orgIdSlug) {
126133
return executeRequest(new ListEmojisRequest(orgIdSlug));
127134
}
128135

136+
/**
137+
* Retrieve all Builds accessible to the current user/API access token, across all Organizations.
138+
* Results will be paged.
139+
*
140+
* @return All Builds accessible to the current user/API access token, across all Organizations. Results
141+
* will be paged if the number of results exceeds 30.
142+
* @throws BuildkiteException if API returns an error response.
143+
*/
129144
public ListBuildsResponse listBuilds() {
130145
return listBuilds(BuildFilters.newBuilder().build());
131146
}
132147

148+
/**
149+
* Retrieve all builds which match the supplied search criteria.
150+
*
151+
* @return All builds which match the supplied search criteria. Results will be paged if the number of results
152+
* exceeds 30 (or the page limit specified in the search criteria).
153+
*
154+
* @throws BuildkiteException if API returns an error response.
155+
*/
133156
public ListBuildsResponse listBuilds(final BuildFiltersBuilder filtersBuilder) {
134157
return listBuilds(filtersBuilder.build());
135158
}
136159

160+
/**
161+
* Retrieve all builds which match the supplied search criteria.
162+
*
163+
* @return All builds which match the supplied search criteria. Results will be paged if the number of results
164+
* exceeds 30 (or the page limit specified in the search criteria).
165+
*
166+
* @throws BuildkiteException if API returns an error response.
167+
*/
137168
public ListBuildsResponse listBuilds(final BuildFilters filters) {
138169
return executeRequest(new ListBuildsRequest(filters));
139170
}
140171

172+
/**
173+
* Retrieve the next page of results from the previously retrieved request.
174+
*
175+
* @param <T> The parsed return object representing the result.
176+
* @param response Previously retrieved result/response to retrieve the next page of results for.
177+
* @return The next page of results.
178+
* @throws InvalidPagingRequestException if no next page exists to retrieve.
179+
* @throws BuildkiteException if API returns an error response.
180+
*/
181+
public <T> T nextPage(final PageableResponse<T> response) {
182+
// Validate
183+
Objects.requireNonNull(response);
184+
final PagingLinks pagingLinks = Objects.requireNonNull(response.getPagingLinks());
185+
if (!pagingLinks.hasNextUrl()) {
186+
throw new InvalidPagingRequestException(
187+
"Requested 'Next' page on response " + response.getClass().getSimpleName() + ", but no Next page is available."
188+
);
189+
}
190+
191+
// Update request with appropriate page options.
192+
final PageOptions pageOptions;
193+
try {
194+
pageOptions = PageOptions.fromUrl(pagingLinks.getNextUrl());
195+
} catch (final IllegalArgumentException ex) {
196+
throw new InvalidPagingRequestException("Unable to parse URL for paging information", ex);
197+
}
198+
final PageableRequest<T> request = response.getOriginalRequest();
199+
request.updatePageOptions(pageOptions);
200+
201+
// Execute and return.
202+
return executeRequest(request);
203+
}
204+
205+
/**
206+
* Retrieve the previous page of results from the previously retrieved request.
207+
*
208+
* @param <T> The parsed return object representing the result.
209+
* @param response Previously retrieved result/response to retrieve the previous page of results for.
210+
* @return The previous page of results.
211+
* @throws InvalidPagingRequestException if no previous page exists to retrieve.
212+
* @throws BuildkiteException if API returns an error response.
213+
*/
214+
public <T> T previousPage(final PageableResponse<T> response) {
215+
// Validate
216+
Objects.requireNonNull(response);
217+
final PagingLinks pagingLinks = Objects.requireNonNull(response.getPagingLinks());
218+
if (!pagingLinks.hasPrevUrl()) {
219+
throw new InvalidPagingRequestException(
220+
"Requested 'Previous' page on response " + response.getClass().getSimpleName() + ", but no Previous page is available."
221+
);
222+
}
223+
224+
// Update request with appropriate page options.
225+
final PageOptions pageOptions;
226+
try {
227+
pageOptions = PageOptions.fromUrl(pagingLinks.getNextUrl());
228+
} catch (final IllegalArgumentException ex) {
229+
throw new InvalidPagingRequestException("Unable to parse URL for paging information", ex);
230+
}
231+
final PageableRequest<T> request = response.getOriginalRequest();
232+
request.updatePageOptions(pageOptions);
233+
234+
// Execute and return.
235+
return executeRequest(request);
236+
}
237+
238+
/**
239+
* Retrieve the first page of results from the previously retrieved request.
240+
*
241+
* @param <T> The parsed return object representing the result.
242+
* @param response Previously retrieved result/response to retrieve the first page of results for.
243+
* @return The first page of results.
244+
* @throws InvalidPagingRequestException if no previous page exists to retrieve.
245+
* @throws BuildkiteException if API returns an error response.
246+
*/
247+
public <T> T firstPage(final PageableResponse<T> response) {
248+
// Validate
249+
Objects.requireNonNull(response);
250+
final PagingLinks pagingLinks = Objects.requireNonNull(response.getPagingLinks());
251+
if (!pagingLinks.hasFirstUrl()) {
252+
throw new InvalidPagingRequestException(
253+
"Requested 'First' page on response " + response.getClass().getSimpleName() + ", but no First page is available."
254+
);
255+
}
256+
257+
// Update request with appropriate page options.
258+
final PageOptions pageOptions;
259+
try {
260+
pageOptions = PageOptions.fromUrl(pagingLinks.getNextUrl());
261+
} catch (final IllegalArgumentException ex) {
262+
throw new InvalidPagingRequestException("Unable to parse URL for paging information", ex);
263+
}
264+
final PageableRequest<T> request = response.getOriginalRequest();
265+
request.updatePageOptions(pageOptions);
266+
267+
// Execute and return.
268+
return executeRequest(request);
269+
}
270+
271+
/**
272+
* Retrieve the last page of results from the previously retrieved request.
273+
*
274+
* @param <T> The parsed return object representing the result.
275+
* @param response Previously retrieved result/response to retrieve the last page of results for.
276+
* @return The last page of results.
277+
* @throws InvalidPagingRequestException if no previous page exists to retrieve.
278+
* @throws BuildkiteException if API returns an error response.
279+
*/
280+
public <T> T lastPage(final PageableResponse<T> response) {
281+
// Validate
282+
Objects.requireNonNull(response);
283+
final PagingLinks pagingLinks = Objects.requireNonNull(response.getPagingLinks());
284+
if (!pagingLinks.hasLastUrl()) {
285+
throw new InvalidPagingRequestException(
286+
"Requested 'Last' page on response " + response.getClass().getSimpleName() + ", but no Last page is available."
287+
);
288+
}
289+
290+
// Update request with appropriate page options.
291+
final PageOptions pageOptions;
292+
try {
293+
pageOptions = PageOptions.fromUrl(pagingLinks.getNextUrl());
294+
} catch (final IllegalArgumentException ex) {
295+
throw new InvalidPagingRequestException("Unable to parse URL for paging information", ex);
296+
}
297+
final PageableRequest<T> request = response.getOriginalRequest();
298+
request.updatePageOptions(pageOptions);
299+
300+
// Execute and return.
301+
return executeRequest(request);
302+
}
303+
141304
/**
142305
* Execute the given request, returning the parsed response, or throwing the appropriate
143306
* exception if an error was returned from the API.
@@ -168,7 +331,7 @@ public <T> T executeRequest(final Request<T> request) throws BuildkiteException
168331
* @throws BuildkiteException relating to specific underlying API error.
169332
*/
170333
private void handleError(final HttpResult errorResult) throws BuildkiteException {
171-
// Attempt to parse error respone.
334+
// Attempt to parse error response.
172335
String errorMessage = null;
173336
try {
174337
final ErrorResponse errorResponse = new ErrorResponseParser().parseResponse(errorResult);
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* Copyright 2023 SourceLab.org https://github.com/SourceLabOrg/Buildkite-Api-Client
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5+
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
6+
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
7+
* persons to whom the Software is furnished to do so, subject to the following conditions:
8+
*
9+
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
10+
* Software.
11+
*
12+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
13+
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
14+
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
15+
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16+
*/
17+
18+
package org.sourcelab.buildkite.api.client.exception;
19+
20+
/**
21+
* Thrown if requested to access a page that is not linked.
22+
*/
23+
public class InvalidPagingRequestException extends BuildkiteException {
24+
/**
25+
* Constructor.
26+
* @param message Error message.
27+
*/
28+
public InvalidPagingRequestException(final String message) {
29+
super(message);
30+
}
31+
32+
/**
33+
* Constructor.
34+
* @param message Error message.
35+
* @param cause Underlying error cause.
36+
*/
37+
public InvalidPagingRequestException(final String message, final IllegalArgumentException cause) {
38+
super(message, cause);
39+
}
40+
}

src/main/java/org/sourcelab/buildkite/api/client/request/BuildFilters.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ public BuildFilters(
8181
this.includeRetriedJobs = includeRetriedJobs;
8282
this.metaData = Collections.unmodifiableMap(new HashMap<>(metaData));
8383
this.states = Collections.unmodifiableSet(new HashSet<>(states));
84-
this.pageOptions = pageOptions;
84+
this.pageOptions = pageOptions == null ? PageOptions.getDefault() : pageOptions;
8585
this.orgIdSlug = orgIdSlug;
8686
this.pipelineIdSlug = pipelineSlugId;
8787
this.buildNumber = buildNumber;

src/main/java/org/sourcelab/buildkite/api/client/request/ListBuildsRequest.java

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
package org.sourcelab.buildkite.api.client.request;
1919

20-
import org.sourcelab.buildkite.api.client.BuildkiteClient;
2120
import org.sourcelab.buildkite.api.client.response.ListBuildsResponse;
2221
import org.sourcelab.buildkite.api.client.response.parser.ListBuildsResponseParser;
2322
import org.sourcelab.buildkite.api.client.response.parser.ResponseParser;
@@ -26,14 +25,21 @@
2625
import java.util.Map;
2726
import java.util.Objects;
2827

29-
public class ListBuildsRequest extends GetRequest<ListBuildsResponse> {
28+
public class ListBuildsRequest extends GetRequest<ListBuildsResponse> implements PageableRequest<ListBuildsResponse> {
3029
private final BuildFilters filters;
30+
private PageOptions pageOptions;
3131

32+
/**
33+
* Constructor.
34+
* @param filters Search Criteria.
35+
*/
3236
public ListBuildsRequest(final BuildFilters filters) {
3337
Objects.requireNonNull(filters);
3438
this.filters = filters;
39+
this.pageOptions = filters.getPageOptions() == null ? PageOptions.getDefault() : filters.getPageOptions();
3540
}
3641

42+
@Override
3743
public String getPath() {
3844
return "/v2/builds";
3945
}
@@ -43,10 +49,8 @@ public RequestParameters getRequestParameters() {
4349
final RequestParametersBuilder builder = RequestParameters.newBuilder();
4450

4551
// Paging options
46-
if (filters.getPageOptions() != null) {
47-
builder.withParameter("per_page", filters.getPageOptions().getPerPage());
48-
builder.withParameter("page", filters.getPageOptions().getPage());
49-
}
52+
builder.withParameter("per_page", pageOptions.getPerPage());
53+
builder.withParameter("page", pageOptions.getPage());
5054

5155
// If we have branches
5256
if (!filters.getBranches().isEmpty()) {
@@ -94,6 +98,11 @@ public RequestParameters getRequestParameters() {
9498

9599
@Override
96100
public ResponseParser<ListBuildsResponse> getResponseParser() {
97-
return new ListBuildsResponseParser();
101+
return new ListBuildsResponseParser(this);
102+
}
103+
104+
@Override
105+
public void updatePageOptions(final PageOptions pageOptions) {
106+
this.pageOptions = Objects.requireNonNull(pageOptions);
98107
}
99108
}

src/main/java/org/sourcelab/buildkite/api/client/request/PageOptions.java

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717

1818
package org.sourcelab.buildkite.api.client.request;
1919

20+
import java.util.Objects;
21+
import java.util.regex.Matcher;
22+
import java.util.regex.Pattern;
23+
2024
/**
2125
* Paging options.
2226
*/
@@ -53,6 +57,39 @@ public PageOptions(int page, int perPage) {
5357
this.perPage = perPage;
5458
}
5559

60+
/**
61+
* Parses the given url and creates a PageOptions instance from the parameters.
62+
*
63+
* @param url The url to parse for per_page and page parameters.
64+
* @return PageOptions instance populated from the supplied url.
65+
* @throws IllegalArgumentException if passed an invalid url.
66+
*/
67+
public static PageOptions fromUrl(final String url) {
68+
Objects.requireNonNull(url);
69+
70+
final Pattern perPagePattern = Pattern.compile(".*[?&]per_page=([0-9]+).*");
71+
final Pattern pagePattern = Pattern.compile(".*[?&]page=([0-9]+).*");
72+
73+
Integer perPage = null;
74+
Integer page = null;
75+
76+
final Matcher perPageMatcher = perPagePattern.matcher(url);
77+
if (perPageMatcher.matches() && perPageMatcher.groupCount() == 1) {
78+
perPage = Integer.parseInt(perPageMatcher.group(1));
79+
}
80+
81+
final Matcher pageMatcher = pagePattern.matcher(url);
82+
if (pageMatcher.matches() && pageMatcher.groupCount() == 1) {
83+
page = Integer.parseInt(pageMatcher.group(1));
84+
}
85+
86+
if (perPage == null || page == null) {
87+
throw new IllegalArgumentException("Unable to parse url " + url);
88+
}
89+
90+
return new PageOptions(page, perPage);
91+
}
92+
5693
public int getPage() {
5794
return page;
5895
}
@@ -64,8 +101,8 @@ public int getPerPage() {
64101
@Override
65102
public String toString() {
66103
return "PageOptions{"
67-
+ "page=" + page
68-
+ ", perPage=" + perPage
69-
+ '}';
104+
+ "page=" + page
105+
+ ", perPage=" + perPage
106+
+ '}';
70107
}
71108
}

0 commit comments

Comments
 (0)