Skip to content

Commit 2f70f10

Browse files
author
Steven Yuan
authored
Support aliases for MiddlewareStack (#961)
`aliases` is introduced to enable refactoring of the middleware stack of existing clients. Instead of only considering `name` of middlewares, all entries in `alias` are also considered for actions like adding, removing.
1 parent 3f302fa commit 2f70f10

File tree

4 files changed

+322
-50
lines changed

4 files changed

+322
-50
lines changed

.changeset/soft-bikes-change.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@smithy/middleware-stack": patch
3+
"@smithy/types": patch
4+
---
5+
6+
Support `aliases` for `MiddlewareStack`

packages/middleware-stack/src/MiddlewareStack.spec.ts

Lines changed: 220 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,19 @@ describe("MiddlewareStack", () => {
4747
priority: "low",
4848
step: "deserialize",
4949
});
50+
stack.add(getConcatMiddleware("H") as DeserializeMiddleware<input, output>, {
51+
aliases: ["h"],
52+
priority: "low",
53+
step: "deserialize",
54+
});
5055
const inner = jest.fn();
5156

5257
const composed = stack.resolve(inner, {} as any);
5358
await composed({ input: [] });
5459

5560
expect(inner.mock.calls.length).toBe(1);
5661
expect(inner).toBeCalledWith({
57-
input: ["A", "B", "C", "D", "E", "F", "G"],
62+
input: ["A", "B", "C", "D", "E", "F", "G", "H"],
5863
});
5964
});
6065

@@ -65,6 +70,22 @@ describe("MiddlewareStack", () => {
6570
expect(() => stack.add(aMW, { name: "A" })).toThrow("Duplicate middleware name 'A'");
6671
});
6772

73+
it("should throw if duplicated name via aliases of existing entry is found", () => {
74+
const stack = constructStack<input, output>();
75+
const aMW = getConcatMiddleware("A");
76+
stack.add(aMW, { aliases: ["ALIAS"] });
77+
expect(() => stack.add(aMW, { name: "ALIAS" })).toThrow("Duplicate middleware name 'ALIAS'");
78+
});
79+
80+
it("should throw if duplicated name via aliases of added entry is found", () => {
81+
const stack = constructStack<input, output>();
82+
const aMW = getConcatMiddleware("ALIAS");
83+
stack.add(aMW, { name: "ALIAS" });
84+
expect(() => stack.add(aMW, { aliases: ["ALIAS"] })).toThrow(
85+
"Duplicate middleware name 'anonymous (a.k.a. ALIAS)'"
86+
);
87+
});
88+
6889
describe("config: override", () => {
6990
it("should override the middleware with same name if override config is set", async () => {
7091
const stack = constructStack<input, output>();
@@ -79,13 +100,59 @@ describe("MiddlewareStack", () => {
79100
});
80101
});
81102

103+
it("should override the middleware with matching alias of existing entry if override config is set", async () => {
104+
const stack = constructStack<input, output>();
105+
stack.add(getConcatMiddleware("A"), { aliases: ["ALIAS"] });
106+
stack.add(getConcatMiddleware("override"), { name: "ALIAS", override: true });
107+
const inner = jest.fn();
108+
const composed = stack.resolve(inner, {} as any);
109+
await composed({ input: [] });
110+
expect(inner.mock.calls.length).toBe(1);
111+
expect(inner).toBeCalledWith({
112+
input: ["override"],
113+
});
114+
});
115+
116+
it("should override the middleware with matching alias of added entry if override config is set", async () => {
117+
const stack = constructStack<input, output>();
118+
stack.add(getConcatMiddleware("A"), { name: "ALIAS" });
119+
stack.add(getConcatMiddleware("override"), { aliases: ["ALIAS"], override: true });
120+
const inner = jest.fn();
121+
const composed = stack.resolve(inner, {} as any);
122+
await composed({ input: [] });
123+
expect(inner.mock.calls.length).toBe(1);
124+
expect(inner).toBeCalledWith({
125+
input: ["override"],
126+
});
127+
});
128+
82129
it("should throw if overriding middleware with same name different position", () => {
83130
const stack = constructStack<input, output>();
84131
stack.add(getConcatMiddleware("A"), { name: "A" });
85132
expect(() =>
86133
stack.add(getConcatMiddleware("override"), { name: "A", step: "serialize", override: true })
87134
).toThrow(
88-
'"A" middleware with normal priority in initialize step cannot be overridden by same-name middleware with normal priority in serialize step.'
135+
'"A" middleware with normal priority in initialize step cannot be overridden by "A" middleware with normal priority in serialize step.'
136+
);
137+
});
138+
139+
it("should throw if overriding middleware with matching alias of existing entry different position", () => {
140+
const stack = constructStack<input, output>();
141+
stack.add(getConcatMiddleware("A"), { aliases: ["ALIAS"] });
142+
expect(() =>
143+
stack.add(getConcatMiddleware("override"), { name: "ALIAS", step: "serialize", override: true })
144+
).toThrow(
145+
'"anonymous (a.k.a. ALIAS)" middleware with normal priority in initialize step cannot be overridden by "ALIAS" middleware with normal priority in serialize step.'
146+
);
147+
});
148+
149+
it("should throw if overriding middleware with matching alias of added entry different position", () => {
150+
const stack = constructStack<input, output>();
151+
stack.add(getConcatMiddleware("A"), { name: "ALIAS" });
152+
expect(() =>
153+
stack.add(getConcatMiddleware("override"), { aliases: ["ALIAS"], step: "serialize", override: true })
154+
).toThrow(
155+
'"ALIAS" middleware with normal priority in initialize step cannot be overridden by "anonymous (a.k.a. ALIAS)" middleware with normal priority in serialize step.'
89156
);
90157
});
91158
});
@@ -95,7 +162,7 @@ describe("MiddlewareStack", () => {
95162
it("should allow adding middleware relatively based relation and order of adding", async () => {
96163
const stack = constructStack<input, output>();
97164
stack.addRelativeTo(getConcatMiddleware("H"), {
98-
name: "H",
165+
aliases: ["AliasH"],
99166
relation: "after",
100167
toMiddleware: "G",
101168
});
@@ -131,11 +198,21 @@ describe("MiddlewareStack", () => {
131198
relation: "before",
132199
toMiddleware: "G",
133200
});
201+
stack.addRelativeTo(getConcatMiddleware("I"), {
202+
aliases: ["AliasI"],
203+
relation: "after",
204+
toMiddleware: "AliasH",
205+
});
206+
stack.addRelativeTo(getConcatMiddleware("J"), {
207+
name: "J",
208+
relation: "after",
209+
toMiddleware: "AliasI",
210+
});
134211
const inner = jest.fn();
135212
const composed = stack.resolve(inner, {} as any);
136213
await composed({ input: [] });
137214
expect(inner.mock.calls.length).toBe(1);
138-
expect(inner).toBeCalledWith({ input: ["A", "B", "C", "D", "E", "F", "G", "H"] });
215+
expect(inner).toBeCalledWith({ input: ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"] });
139216
});
140217

141218
it("should add relative middleware within the scope of adjacent absolute middleware", async () => {
@@ -149,14 +226,24 @@ describe("MiddlewareStack", () => {
149226
stack.addRelativeTo(getConcatMiddleware("C"), {
150227
name: "C",
151228
relation: "before",
152-
toMiddleware: "D",
229+
toMiddleware: "AliasD",
153230
});
154-
stack.add(getConcatMiddleware("D"), { name: "D" });
231+
stack.add(getConcatMiddleware("D"), { aliases: ["AliasD"] });
232+
stack.addRelativeTo(getConcatMiddleware("F"), {
233+
aliases: ["AliasF"],
234+
relation: "after",
235+
toMiddleware: "AliasD",
236+
});
237+
stack.addRelativeTo(getConcatMiddleware("E"), {
238+
relation: "before",
239+
toMiddleware: "AliasF",
240+
});
241+
stack.add(getConcatMiddleware("G"), { name: "G" });
155242
const inner = jest.fn();
156243
const composed = stack.resolve(inner, {} as any);
157244
await composed({ input: [] });
158245
expect(inner.mock.calls.length).toBe(1);
159-
expect(inner).toBeCalledWith({ input: ["A", "B", "C", "D"] });
246+
expect(inner).toBeCalledWith({ input: ["A", "B", "C", "D", "E", "F", "G"] });
160247
});
161248

162249
it("should not add self-referenced relative middleware", async () => {
@@ -173,6 +260,11 @@ describe("MiddlewareStack", () => {
173260
});
174261
stack.addRelativeTo(getConcatMiddleware("C"), {
175262
name: "C",
263+
relation: "before",
264+
toMiddleware: "AliasD",
265+
});
266+
stack.addRelativeTo(getConcatMiddleware("D"), {
267+
aliases: ["AliasD"],
176268
relation: "after",
177269
toMiddleware: "A",
178270
});
@@ -219,6 +311,52 @@ describe("MiddlewareStack", () => {
219311
});
220312
});
221313

314+
it("should override the middleware with matching alias of existing entry if override config is set", async () => {
315+
const stack = constructStack<input, output>();
316+
stack.add(getConcatMiddleware("A"), { name: "A" });
317+
stack.addRelativeTo(getConcatMiddleware("B"), {
318+
aliases: ["ALIAS"],
319+
relation: "after",
320+
toMiddleware: "A",
321+
});
322+
stack.addRelativeTo(getConcatMiddleware("override"), {
323+
name: "ALIAS",
324+
relation: "after",
325+
toMiddleware: "A",
326+
override: true,
327+
});
328+
const inner = jest.fn();
329+
const composed = stack.resolve(inner, {} as any);
330+
await composed({ input: [] });
331+
expect(inner.mock.calls.length).toBe(1);
332+
expect(inner).toBeCalledWith({
333+
input: ["A", "override"],
334+
});
335+
});
336+
337+
it("should override the middleware with matching alias of added entry if override config is set", async () => {
338+
const stack = constructStack<input, output>();
339+
stack.add(getConcatMiddleware("A"), { name: "A" });
340+
stack.addRelativeTo(getConcatMiddleware("ALIAS"), {
341+
name: "ALIAS",
342+
relation: "after",
343+
toMiddleware: "A",
344+
});
345+
stack.addRelativeTo(getConcatMiddleware("override"), {
346+
aliases: ["ALIAS"],
347+
relation: "after",
348+
toMiddleware: "A",
349+
override: true,
350+
});
351+
const inner = jest.fn();
352+
const composed = stack.resolve(inner, {} as any);
353+
await composed({ input: [] });
354+
expect(inner.mock.calls.length).toBe(1);
355+
expect(inner).toBeCalledWith({
356+
input: ["A", "override"],
357+
});
358+
});
359+
222360
it("should throw if overriding middleware with same name different position", () => {
223361
const stack = constructStack<input, output>();
224362
stack.add(getConcatMiddleware("A"), { name: "A" });
@@ -230,8 +368,46 @@ describe("MiddlewareStack", () => {
230368
toMiddleware: "A",
231369
override: true,
232370
})
371+
).toThrow('"B" middleware after "A" middleware cannot be overridden by "B" middleware before "A" middleware.');
372+
});
373+
374+
it("should throw if overriding middleware with matching alias of existing entry different position", () => {
375+
const stack = constructStack<input, output>();
376+
stack.add(getConcatMiddleware("A"), { name: "A" });
377+
stack.addRelativeTo(getConcatMiddleware("B"), {
378+
aliases: ["ALIAS"],
379+
relation: "after",
380+
toMiddleware: "A",
381+
});
382+
expect(() =>
383+
stack.addRelativeTo(getConcatMiddleware("override"), {
384+
name: "ALIAS",
385+
relation: "before",
386+
toMiddleware: "A",
387+
override: true,
388+
})
389+
).toThrow(
390+
'"anonymous (a.k.a. ALIAS)" middleware after "A" middleware cannot be overridden by "ALIAS" middleware before "A" middleware.'
391+
);
392+
});
393+
394+
it("should throw if overriding middleware with matching alias of added entry different position", () => {
395+
const stack = constructStack<input, output>();
396+
stack.add(getConcatMiddleware("A"), { name: "A" });
397+
stack.addRelativeTo(getConcatMiddleware("ALIAS"), {
398+
name: "ALIAS",
399+
relation: "after",
400+
toMiddleware: "A",
401+
});
402+
expect(() =>
403+
stack.addRelativeTo(getConcatMiddleware("override"), {
404+
aliases: ["ALIAS"],
405+
relation: "before",
406+
toMiddleware: "A",
407+
override: true,
408+
})
233409
).toThrow(
234-
'"B" middleware after "A" middleware cannot be overridden by same-name middleware before "A" middleware.'
410+
'"ALIAS" middleware after "A" middleware cannot be overridden by "anonymous (a.k.a. ALIAS)" middleware before "A" middleware.'
235411
);
236412
});
237413
});
@@ -246,19 +422,25 @@ describe("MiddlewareStack", () => {
246422
name: "A",
247423
priority: "high",
248424
});
425+
stack.add(getConcatMiddleware("C"), {
426+
aliases: ["AliasC"],
427+
});
249428
const secondStack = stack.clone();
250429
const inner = jest.fn();
251430
await secondStack.resolve(inner, {} as any)({ input: [] });
252431
expect(inner.mock.calls.length).toBe(1);
253-
expect(inner).toBeCalledWith({ input: ["A", "B"] });
432+
expect(inner).toBeCalledWith({ input: ["A", "B", "C"] });
254433
// validate adding middleware to cloned stack won't affect the original stack.
255434
inner.mockClear();
256-
secondStack.add(getConcatMiddleware("C"));
435+
secondStack.add(getConcatMiddleware("D"));
436+
secondStack.add(getConcatMiddleware("E"), {
437+
aliases: ["AliasE"],
438+
});
257439
await secondStack.resolve(inner, {} as any)({ input: [] });
258-
expect(inner).toBeCalledWith({ input: ["A", "B", "C"] });
440+
expect(inner).toBeCalledWith({ input: ["A", "B", "C", "D", "E"] });
259441
inner.mockClear();
260442
await stack.resolve(inner, {} as any)({ input: [] });
261-
expect(inner).toBeCalledWith({ input: ["A", "B"] });
443+
expect(inner).toBeCalledWith({ input: ["A", "B", "C"] });
262444
});
263445
});
264446

@@ -267,7 +449,7 @@ describe("MiddlewareStack", () => {
267449
const stack = constructStack<input, output>();
268450
stack.add(getConcatMiddleware("A"));
269451
stack.add(getConcatMiddleware("B"), {
270-
name: "B",
452+
aliases: ["AliasB"],
271453
priority: "low",
272454
});
273455

@@ -278,7 +460,7 @@ describe("MiddlewareStack", () => {
278460
});
279461
secondStack.addRelativeTo(getConcatMiddleware("C"), {
280462
relation: "after",
281-
toMiddleware: "B",
463+
toMiddleware: "AliasB",
282464
});
283465

284466
const inner = jest.fn();
@@ -328,12 +510,34 @@ describe("MiddlewareStack", () => {
328510
expect(inner).toBeCalledWith({ input: ["don't remove me"] });
329511
});
330512

513+
it("should remove middleware by alias of existing entry", async () => {
514+
const stack = constructStack<input, output>();
515+
stack.add(getConcatMiddleware("don't remove me"), { name: "notRemove" });
516+
stack.addRelativeTo(getConcatMiddleware("remove me!"), {
517+
relation: "after",
518+
toMiddleware: "notRemove",
519+
aliases: ["toRemove"],
520+
});
521+
522+
await stack.resolve(({ input }: FinalizeHandlerArguments<Array<string>>) => {
523+
expect(input.sort()).toEqual(["don't remove me", "remove me!"]);
524+
return Promise.resolve({ response: {} });
525+
}, {} as any)({ input: [] });
526+
527+
stack.remove("toRemove");
528+
529+
const inner = jest.fn();
530+
await stack.resolve(inner, {} as any)({ input: [] });
531+
expect(inner.mock.calls.length).toBe(1);
532+
expect(inner).toBeCalledWith({ input: ["don't remove me"] });
533+
});
534+
331535
it("should remove middleware by reference", async () => {
332536
const stack = constructStack<input, output>();
333537
const mw = getConcatMiddleware("remove all references of me");
334538
stack.add(mw, { name: "toRemove1" });
335539
stack.add(getConcatMiddleware("don't remove me!"));
336-
stack.add(mw, { name: "toRemove2" });
540+
stack.add(mw, { aliases: ["toRemove2"] });
337541
stack.remove(mw);
338542

339543
const inner = jest.fn();
@@ -351,6 +555,7 @@ describe("MiddlewareStack", () => {
351555
tags: ["foo", "bar"],
352556
});
353557
stack.addRelativeTo(getConcatMiddleware("remove me!"), {
558+
aliases: ["remove me!"],
354559
relation: "after",
355560
toMiddleware: "not removed",
356561
tags: ["foo", "bar", "baz"],

0 commit comments

Comments
 (0)