Skip to content

Commit 21affd4

Browse files
committed
fix: handle multi-part token paths in paginator
1 parent ceb489e commit 21affd4

File tree

3 files changed

+133
-1
lines changed

3 files changed

+133
-1
lines changed

.changeset/four-steaks-sip.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@smithy/core": patch
3+
---
4+
5+
handle multi-part input token in paginator
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { PaginationConfiguration } from "@smithy/types";
2+
3+
import { createPaginator } from "./createPaginator";
4+
5+
describe(createPaginator.name, () => {
6+
class Client {
7+
private pages = 5;
8+
async send(command: Command) {
9+
if (--this.pages > 0) {
10+
return {
11+
outToken: {
12+
outToken2: {
13+
outToken3: "TOKEN_VALUE",
14+
},
15+
},
16+
};
17+
}
18+
return {};
19+
}
20+
}
21+
class Command {
22+
public constructor(public input: any) {
23+
if (typeof input.inToken === "object") {
24+
expect(input).toEqual({
25+
sizeToken: 100,
26+
inToken: {
27+
outToken2: {
28+
outToken3: "TOKEN_VALUE",
29+
},
30+
},
31+
});
32+
} else {
33+
expect(input).toEqual({
34+
sizeToken: 100,
35+
inToken: "TOKEN_VALUE",
36+
});
37+
}
38+
}
39+
}
40+
41+
it("should create a paginator", async () => {
42+
const paginate = createPaginator<PaginationConfiguration, { inToken?: string }, { outToken: string }>(
43+
Client,
44+
Command,
45+
"inToken",
46+
"outToken",
47+
"sizeToken"
48+
);
49+
50+
let pages = 0;
51+
52+
for await (const page of paginate(
53+
{
54+
client: new Client() as any,
55+
pageSize: 100,
56+
startingToken: {
57+
outToken2: {
58+
outToken3: "TOKEN_VALUE",
59+
},
60+
},
61+
},
62+
{}
63+
)) {
64+
pages += 1;
65+
if (pages === 5) {
66+
expect(page.outToken).toBeUndefined();
67+
} else {
68+
expect(page.outToken).toEqual({
69+
outToken2: {
70+
outToken3: "TOKEN_VALUE",
71+
},
72+
});
73+
}
74+
}
75+
76+
expect(pages).toEqual(5);
77+
});
78+
79+
it("should handle deep paths", async () => {
80+
const paginate = createPaginator<
81+
PaginationConfiguration,
82+
{ inToken?: string },
83+
{
84+
outToken: {
85+
outToken2: {
86+
outToken3: string;
87+
};
88+
};
89+
}
90+
>(Client, Command, "inToken", "outToken.outToken2.outToken3", "sizeToken");
91+
92+
let pages = 0;
93+
94+
for await (const page of paginate(
95+
{
96+
client: new Client() as any,
97+
pageSize: 100,
98+
startingToken: "TOKEN_VALUE",
99+
},
100+
{}
101+
)) {
102+
pages += 1;
103+
if (pages === 5) {
104+
expect(page.outToken).toBeUndefined();
105+
} else {
106+
expect(page.outToken.outToken2.outToken3).toEqual("TOKEN_VALUE");
107+
}
108+
}
109+
110+
expect(pages).toEqual(5);
111+
});
112+
});

packages/core/src/pagination/createPaginator.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,25 @@ export function createPaginator<
4949
}
5050
yield page;
5151
const prevToken = token;
52-
token = (page as any)[outputTokenName];
52+
token = get(page, outputTokenName);
5353
hasNext = !!(token && (!config.stopOnSameToken || token !== prevToken));
5454
}
5555
// @ts-ignore
5656
return undefined;
5757
};
5858
}
59+
60+
/**
61+
* @internal
62+
*/
63+
const get = (fromObject: any, path: string): any => {
64+
let cursor = fromObject;
65+
const pathComponents = path.split(".");
66+
for (const step of pathComponents) {
67+
if (!cursor || typeof cursor !== "object") {
68+
return undefined;
69+
}
70+
cursor = cursor[step];
71+
}
72+
return cursor;
73+
};

0 commit comments

Comments
 (0)