Skip to content

Commit 61b574c

Browse files
committed
mirage: Add seek pagination support for GET /api/v1/crates/:name/versions
1 parent 36af5fb commit 61b574c

File tree

2 files changed

+104
-2
lines changed

2 files changed

+104
-2
lines changed

mirage/route-handlers/crates.js

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ export function register(server) {
159159
if (!crate) return notFound();
160160

161161
let versions = crate.versions;
162-
let { nums, sort } = request.queryParams;
162+
let { nums, sort, per_page, seek } = request.queryParams;
163163
if (nums) {
164164
versions = versions.filter(version => nums.includes(version.num));
165165
}
@@ -170,9 +170,32 @@ export function register(server) {
170170
let total = versions.length;
171171
let include = request.queryParams?.include ?? '';
172172
let release_tracks = include.split(',').includes('release_tracks') && releaseTracks(crate.versions);
173+
174+
// seek pagination
175+
// A simplified seek encoding is applied here for testing purposes only. It should be opaque
176+
// in real-world scenarios.
177+
let next_seek = null;
178+
if (per_page != null) {
179+
if (seek != null) {
180+
let idx = versions.models.findIndex(it => it.num === seek);
181+
versions = versions.slice(idx + 1);
182+
}
183+
versions = versions.slice(0, per_page);
184+
185+
if (versions.length >= per_page) {
186+
let last = versions.models.at(-1);
187+
next_seek = last.num;
188+
}
189+
}
190+
let next_page = null;
191+
if (next_seek) {
192+
next_page = new URLSearchParams({ ...request.queryParams, seek: next_seek });
193+
next_page = `?${next_page}`;
194+
}
195+
173196
let resp = {
174197
...this.serialize(versions),
175-
meta: { total, next_page: null },
198+
meta: { total, next_page },
176199
};
177200
if (release_tracks && Object.keys(release_tracks).length !== 0) {
178201
resp.meta.release_tracks = release_tracks;

tests/mirage/crates/versions/list-test.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,85 @@ module('Mirage | GET /api/v1/crates/:name/versions', function (hooks) {
147147
}
148148
});
149149

150+
test('supports seek pagination', async function (assert) {
151+
let user = this.server.create('user');
152+
let crate = this.server.create('crate', { name: 'rand' });
153+
this.server.create('version', { crate, num: '1.0.0', created_at: '2010-06-16T21:30:45Z' });
154+
this.server.create('version', { crate, num: '1.2.0', rust_version: '1.69', created_at: '2010-06-16T21:30:46Z' });
155+
this.server.create('version', { crate, num: '1.1.0', publishedBy: user, created_at: '2010-06-16T21:30:47Z' });
156+
157+
async function seek_forwards(queryParams) {
158+
let calls = 0;
159+
let next_page;
160+
let responses = [];
161+
let base_url = '/api/v1/crates/rand/versions';
162+
let params = new URLSearchParams(queryParams);
163+
let url = `${base_url}?${params}`;
164+
while ((calls == 0 || next_page) && calls < 10) {
165+
if (next_page) {
166+
url = `${base_url}${next_page}`;
167+
}
168+
let response = await fetch(url);
169+
calls += 1;
170+
assert.strictEqual(response.status, 200);
171+
let json = await response.json();
172+
responses.push(json);
173+
next_page = json.meta.next_page;
174+
if (next_page == null) {
175+
break;
176+
}
177+
}
178+
return responses;
179+
}
180+
181+
// sort by `semver` by default
182+
{
183+
let responses = await seek_forwards({ per_page: 1 });
184+
assert.deepEqual(
185+
responses.map(it => it.versions.map(v => v.num)),
186+
[['1.2.0'], ['1.1.0'], ['1.0.0'], []],
187+
);
188+
assert.deepEqual(
189+
responses.map(it => it.meta.next_page),
190+
['?per_page=1&seek=1.2.0', '?per_page=1&seek=1.1.0', '?per_page=1&seek=1.0.0', null],
191+
);
192+
}
193+
194+
{
195+
let responses = await seek_forwards({ per_page: 1, sort: 'semver' });
196+
assert.deepEqual(
197+
responses.map(it => it.versions.map(v => v.num)),
198+
[['1.2.0'], ['1.1.0'], ['1.0.0'], []],
199+
);
200+
assert.deepEqual(
201+
responses.map(it => it.meta.next_page),
202+
[
203+
'?per_page=1&sort=semver&seek=1.2.0',
204+
'?per_page=1&sort=semver&seek=1.1.0',
205+
'?per_page=1&sort=semver&seek=1.0.0',
206+
null,
207+
],
208+
);
209+
}
210+
211+
{
212+
let responses = await seek_forwards({ per_page: 1, sort: 'date' });
213+
assert.deepEqual(
214+
responses.map(it => it.versions.map(v => v.num)),
215+
[['1.1.0'], ['1.2.0'], ['1.0.0'], []],
216+
);
217+
assert.deepEqual(
218+
responses.map(it => it.meta.next_page),
219+
[
220+
'?per_page=1&sort=date&seek=1.1.0',
221+
'?per_page=1&sort=date&seek=1.2.0',
222+
'?per_page=1&sort=date&seek=1.0.0',
223+
null,
224+
],
225+
);
226+
}
227+
});
228+
150229
test('supports multiple `ids[]` parameters', async function (assert) {
151230
let user = this.server.create('user');
152231
let crate = this.server.create('crate', { name: 'rand' });

0 commit comments

Comments
 (0)