Skip to content

Commit 679066d

Browse files
committed
remove built in webhook response builder
1 parent 126493d commit 679066d

File tree

10 files changed

+84
-84
lines changed

10 files changed

+84
-84
lines changed

.changeset/new-paws-win.md

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
---
2+
"@saleor/app-sdk": major
3+
---
4+
5+
Removed `ctx` parameter from SyncWebhookHandler and replace with standalone `buildSyncWebhookResponsePayload` function
6+
7+
Before
8+
9+
```typescript
10+
11+
new SaleorSyncWebhook(...).createHandler(
12+
req, res, ctx
13+
) {
14+
15+
const typeSafePayload = ctx.buildResponse({
16+
// this must be valid response
17+
})
18+
}
19+
```
20+
21+
After
22+
23+
```typescript
24+
import { buildSyncWebhookResponsePayload } from "@saleor/app-sdk/handlers/shared";
25+
26+
new SaleorSyncWebhook(...).createHandler(
27+
req, res, ctx
28+
)
29+
{
30+
31+
const typeSafePayload = buildSyncWebhookResponsePayload<"ORDER_CALCULATE_TAXES">({
32+
// this must be valid shape
33+
})
34+
}
35+
```
36+
37+
This change reduces complexity of TypeScript generics and make it easier to build abstractions on top of built-in handlers

src/handlers/platforms/aws-lambda/saleor-webhooks/saleor-sync-webhook.test.ts

+12-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { afterEach, describe, expect, it, vi } from "vitest";
22

3-
import { FormatWebhookErrorResult } from "@/handlers/shared";
3+
import { buildSyncWebhookResponsePayload, FormatWebhookErrorResult } from "@/handlers/shared";
44
import { SaleorWebhookValidator } from "@/handlers/shared/saleor-webhook-validator";
55
import { MockAPL } from "@/test-utils/mock-apl";
66

@@ -27,20 +27,20 @@ describe("AWS Lambda SaleorSyncWebhook", () => {
2727
it("returns valid URL when baseUrl has Lambda stage", () => {
2828
const webhook = new SaleorSyncWebhook(webhookConfig);
2929
expect(webhook.getWebhookManifest("https://aws-lambda.com/prod").targetUrl).toBe(
30-
"https://aws-lambda.com/prod/api/webhooks/checkout-calculate-taxes"
30+
"https://aws-lambda.com/prod/api/webhooks/checkout-calculate-taxes",
3131
);
3232
expect(webhook.getWebhookManifest("https://aws-lambda.com/prod/").targetUrl).toBe(
33-
"https://aws-lambda.com/prod/api/webhooks/checkout-calculate-taxes"
33+
"https://aws-lambda.com/prod/api/webhooks/checkout-calculate-taxes",
3434
);
3535
expect(webhook.getWebhookManifest("https://aws-lambda.com/test").targetUrl).toBe(
36-
"https://aws-lambda.com/test/api/webhooks/checkout-calculate-taxes"
36+
"https://aws-lambda.com/test/api/webhooks/checkout-calculate-taxes",
3737
);
3838
});
3939

4040
it("returns valid URL when baseURl doesn't have lambda stage ($default stage)", () => {
4141
const webhook = new SaleorSyncWebhook(webhookConfig);
4242
expect(webhook.getWebhookManifest("https://aws-lambda.com/").targetUrl).toBe(
43-
"https://aws-lambda.com/api/webhooks/checkout-calculate-taxes"
43+
"https://aws-lambda.com/api/webhooks/checkout-calculate-taxes",
4444
);
4545
});
4646

@@ -50,16 +50,16 @@ describe("AWS Lambda SaleorSyncWebhook", () => {
5050
webhookPath: `/${webhookConfig.webhookPath}`,
5151
});
5252
expect(webhook.getWebhookManifest("https://aws-lambda.com/prod").targetUrl).toBe(
53-
"https://aws-lambda.com/prod/api/webhooks/checkout-calculate-taxes"
53+
"https://aws-lambda.com/prod/api/webhooks/checkout-calculate-taxes",
5454
);
5555
expect(webhook.getWebhookManifest("https://aws-lambda.com/prod/").targetUrl).toBe(
56-
"https://aws-lambda.com/prod/api/webhooks/checkout-calculate-taxes"
56+
"https://aws-lambda.com/prod/api/webhooks/checkout-calculate-taxes",
5757
);
5858
expect(webhook.getWebhookManifest("https://aws-lambda.com/test").targetUrl).toBe(
59-
"https://aws-lambda.com/test/api/webhooks/checkout-calculate-taxes"
59+
"https://aws-lambda.com/test/api/webhooks/checkout-calculate-taxes",
6060
);
6161
expect(webhook.getWebhookManifest("https://aws-lambda.com/").targetUrl).toBe(
62-
"https://aws-lambda.com/api/webhooks/checkout-calculate-taxes"
62+
"https://aws-lambda.com/api/webhooks/checkout-calculate-taxes",
6363
);
6464
});
6565
});
@@ -88,15 +88,15 @@ describe("AWS Lambda SaleorSyncWebhook", () => {
8888

8989
const handler: AwsLambdaSyncWebhookHandler<Payload> = vi
9090
.fn()
91-
.mockImplementation(async (_event, _context, ctx) => ({
91+
.mockImplementation(async () => ({
9292
statusCode: 200,
9393
body: JSON.stringify(
94-
ctx.buildResponse({
94+
buildSyncWebhookResponsePayload<"CHECKOUT_CALCULATE_TAXES">({
9595
lines: [{ tax_rate: 8, total_net_amount: 10, total_gross_amount: 10.8 }],
9696
shipping_price_gross_amount: 2.16,
9797
shipping_tax_rate: 8,
9898
shipping_price_net_amount: 2,
99-
})
99+
}),
100100
),
101101
}));
102102
const wrappedHandler = saleorSyncWebhook.createHandler(handler);
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,25 @@
1-
import { SyncWebhookInjectedContext } from "@/handlers/shared";
2-
import { buildSyncWebhookResponsePayload } from "@/handlers/shared/sync-webhook-response-builder";
31
import { SyncWebhookEventType } from "@/types";
42

53
import { AWSLambdaHandler } from "../platform-adapter";
64
import { AwsLambdaWebhookHandler, SaleorWebApiWebhook, WebhookConfig } from "./saleor-webhook";
75

8-
export type AwsLambdaSyncWebhookHandler<
9-
TPayload,
10-
TEvent extends SyncWebhookEventType = SyncWebhookEventType
11-
> = AwsLambdaWebhookHandler<TPayload, SyncWebhookInjectedContext<TEvent>>;
6+
export type AwsLambdaSyncWebhookHandler<TPayload> = AwsLambdaWebhookHandler<TPayload>;
127

138
export class SaleorSyncWebhook<
149
TPayload = unknown,
15-
TEvent extends SyncWebhookEventType = SyncWebhookEventType
16-
> extends SaleorWebApiWebhook<TPayload, SyncWebhookInjectedContext<TEvent>> {
10+
TEvent extends SyncWebhookEventType = SyncWebhookEventType,
11+
> extends SaleorWebApiWebhook<TPayload> {
1712
readonly event: TEvent;
1813

1914
protected readonly eventType = "sync" as const;
2015

21-
protected extraContext = {
22-
buildResponse: buildSyncWebhookResponsePayload,
23-
};
24-
2516
constructor(configuration: WebhookConfig<TEvent>) {
2617
super(configuration);
2718

2819
this.event = configuration.event;
2920
}
3021

31-
createHandler(handlerFn: AwsLambdaSyncWebhookHandler<TPayload, TEvent>): AWSLambdaHandler {
22+
createHandler(handlerFn: AwsLambdaSyncWebhookHandler<TPayload>): AWSLambdaHandler {
3223
return super.createHandler(handlerFn);
3324
}
3425
}

src/handlers/platforms/aws-lambda/saleor-webhooks/saleor-webhook.ts

+7-8
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,21 @@ export type WebhookConfig<Event = AsyncWebhookEventType | SyncWebhookEventType>
1616
GenericWebhookConfig<AwsLambdaHandlerInput, Event>;
1717

1818
/** Function type provided by consumer in `SaleorWebApiWebhook.createHandler` */
19-
export type AwsLambdaWebhookHandler<TPayload = unknown, TExtras = {}> = (
19+
export type AwsLambdaWebhookHandler<TPayload = unknown> = (
2020
event: AwsLambdaHandlerInput,
2121
context: Context,
22-
ctx: WebhookContext<TPayload> & TExtras
22+
ctx: WebhookContext<TPayload>,
2323
) => Promise<APIGatewayProxyStructuredResultV2> | APIGatewayProxyStructuredResultV2;
2424

25-
export abstract class SaleorWebApiWebhook<
26-
TPayload = unknown,
27-
TExtras extends Record<string, unknown> = {}
28-
> extends GenericSaleorWebhook<AwsLambdaHandlerInput, TPayload, TExtras> {
25+
export abstract class SaleorWebApiWebhook<TPayload = unknown> extends GenericSaleorWebhook<
26+
AwsLambdaHandlerInput,
27+
TPayload
28+
> {
2929
/**
3030
* Wraps provided function, to ensure incoming request comes from registered Saleor instance.
3131
* Also provides additional `context` object containing typed payload and request properties.
3232
*/
33-
createHandler(handlerFn: AwsLambdaWebhookHandler<TPayload, TExtras>): AWSLambdaHandler {
33+
createHandler(handlerFn: AwsLambdaWebhookHandler<TPayload>): AWSLambdaHandler {
3434
return async (event, context) => {
3535
const adapter = new AwsLambdaAdapter(event, context);
3636
const prepareRequestResult = await super.prepareRequest<AwsLambdaAdapter>(adapter);
@@ -41,7 +41,6 @@ export abstract class SaleorWebApiWebhook<
4141

4242
debug("Incoming request validated. Call handlerFn");
4343
return handlerFn(event, context, {
44-
...(this.extraContext ?? ({} as TExtras)),
4544
...prepareRequestResult.context,
4645
});
4746
};

src/handlers/platforms/fetch-api/saleor-webhooks/saleor-sync-webhook.test.ts

+10-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { afterEach, describe, expect, it, vi } from "vitest";
22

3-
import { FormatWebhookErrorResult } from "@/handlers/shared";
3+
import { buildSyncWebhookResponsePayload, FormatWebhookErrorResult } from "@/handlers/shared";
44
import { SaleorWebhookValidator } from "@/handlers/shared/saleor-webhook-validator";
55
import { MockAPL } from "@/test-utils/mock-apl";
66

@@ -41,17 +41,15 @@ describe("Web API SaleorSyncWebhook", () => {
4141
},
4242
});
4343

44-
const handler = vi
45-
.fn<WebApiSyncWebhookHandler<Payload>>()
46-
.mockImplementation((_request, ctx) => {
47-
const responsePayload = ctx.buildResponse({
48-
lines: [{ tax_rate: 8, total_net_amount: 10, total_gross_amount: 1.08 }],
49-
shipping_price_gross_amount: 2,
50-
shipping_tax_rate: 8,
51-
shipping_price_net_amount: 1,
52-
});
53-
return new Response(JSON.stringify(responsePayload), { status: 200 });
44+
const handler = vi.fn<WebApiSyncWebhookHandler<Payload>>().mockImplementation(() => {
45+
const responsePayload = buildSyncWebhookResponsePayload<"ORDER_CALCULATE_TAXES">({
46+
lines: [{ tax_rate: 8, total_net_amount: 10, total_gross_amount: 1.08 }],
47+
shipping_price_gross_amount: 2,
48+
shipping_tax_rate: 8,
49+
shipping_price_net_amount: 1,
5450
});
51+
return new Response(JSON.stringify(responsePayload), { status: 200 });
52+
});
5553

5654
const saleorSyncWebhook = new SaleorSyncWebhook<Payload>(webhookConfiguration);
5755

@@ -70,7 +68,7 @@ describe("Web API SaleorSyncWebhook", () => {
7068
shipping_price_gross_amount: 2,
7169
shipping_tax_rate: 8,
7270
shipping_price_net_amount: 1,
73-
})
71+
}),
7472
);
7573
});
7674

Original file line numberDiff line numberDiff line change
@@ -1,34 +1,25 @@
1-
import { SyncWebhookInjectedContext } from "@/handlers/shared";
2-
import { buildSyncWebhookResponsePayload } from "@/handlers/shared/sync-webhook-response-builder";
31
import { SyncWebhookEventType } from "@/types";
42

53
import { WebApiHandler } from "../platform-adapter";
64
import { SaleorWebApiWebhook, WebApiWebhookHandler, WebhookConfig } from "./saleor-webhook";
75

8-
export type WebApiSyncWebhookHandler<
9-
TPayload,
10-
TEvent extends SyncWebhookEventType = SyncWebhookEventType
11-
> = WebApiWebhookHandler<TPayload, SyncWebhookInjectedContext<TEvent>>;
6+
export type WebApiSyncWebhookHandler<TPayload> = WebApiWebhookHandler<TPayload>;
127

138
export class SaleorSyncWebhook<
149
TPayload = unknown,
15-
TEvent extends SyncWebhookEventType = SyncWebhookEventType
16-
> extends SaleorWebApiWebhook<TPayload, SyncWebhookInjectedContext<TEvent>> {
10+
TEvent extends SyncWebhookEventType = SyncWebhookEventType,
11+
> extends SaleorWebApiWebhook<TPayload> {
1712
readonly event: TEvent;
1813

1914
protected readonly eventType = "sync" as const;
2015

21-
protected extraContext = {
22-
buildResponse: buildSyncWebhookResponsePayload,
23-
};
24-
2516
constructor(configuration: WebhookConfig<TEvent>) {
2617
super(configuration);
2718

2819
this.event = configuration.event;
2920
}
3021

31-
createHandler(handlerFn: WebApiSyncWebhookHandler<TPayload, TEvent>): WebApiHandler {
22+
createHandler(handlerFn: WebApiSyncWebhookHandler<TPayload>): WebApiHandler {
3223
return super.createHandler(handlerFn);
3324
}
3425
}

src/handlers/platforms/next/saleor-webhooks/saleor-sync-webhook.test.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { createMocks } from "node-mocks-http";
22
import { afterEach, describe, expect, it, vi } from "vitest";
33

4-
import { WebhookError } from "@/handlers/shared";
4+
import { buildSyncWebhookResponsePayload, WebhookError } from "@/handlers/shared";
55
import { SaleorWebhookValidator } from "@/handlers/shared/saleor-webhook-validator";
66
import { MockAPL } from "@/test-utils/mock-apl";
77

@@ -31,7 +31,7 @@ describe("Next.js SaleorSyncWebhook", () => {
3131
expect(manifest).toEqual(
3232
expect.objectContaining({
3333
targetUrl: `${baseUrl}/${validSyncWebhookConfiguration.webhookPath}`,
34-
})
34+
}),
3535
);
3636
});
3737

@@ -66,8 +66,8 @@ describe("Next.js SaleorSyncWebhook", () => {
6666
});
6767

6868
const saleorSyncWebhook = new SaleorSyncWebhook(validSyncWebhookConfiguration);
69-
const testHandler: NextJsWebhookHandler = vi.fn().mockImplementation((_req, res, ctx) => {
70-
const responsePayload = ctx.buildResponse({
69+
const testHandler: NextJsWebhookHandler = vi.fn().mockImplementation((_req, res) => {
70+
const responsePayload = buildSyncWebhookResponsePayload({
7171
lines: [{ tax_rate: 8, total_net_amount: 10, total_gross_amount: 1.08 }],
7272
shipping_price_gross_amount: 2,
7373
shipping_tax_rate: 8,
@@ -88,7 +88,7 @@ describe("Next.js SaleorSyncWebhook", () => {
8888
shipping_price_gross_amount: 2,
8989
shipping_tax_rate: 8,
9090
shipping_price_net_amount: 1,
91-
})
91+
}),
9292
);
9393
});
9494

Original file line numberDiff line numberDiff line change
@@ -1,35 +1,26 @@
11
import { NextApiHandler } from "next";
22

3-
import { SyncWebhookInjectedContext } from "@/handlers/shared";
4-
import { buildSyncWebhookResponsePayload } from "@/handlers/shared/sync-webhook-response-builder";
53
import { SyncWebhookEventType } from "@/types";
64

75
import { NextJsWebhookHandler, SaleorWebhook, WebhookConfig } from "./saleor-webhook";
86

9-
export type NextJsSyncWebhookHandler<
10-
TPayload,
11-
TEvent extends SyncWebhookEventType = SyncWebhookEventType
12-
> = NextJsWebhookHandler<TPayload, SyncWebhookInjectedContext<TEvent>>;
7+
export type NextJsSyncWebhookHandler<TPayload> = NextJsWebhookHandler<TPayload>;
138

149
export class SaleorSyncWebhook<
1510
TPayload = unknown,
16-
TEvent extends SyncWebhookEventType = SyncWebhookEventType
17-
> extends SaleorWebhook<TPayload, SyncWebhookInjectedContext<TEvent>> {
11+
TEvent extends SyncWebhookEventType = SyncWebhookEventType,
12+
> extends SaleorWebhook<TPayload> {
1813
readonly event: TEvent;
1914

2015
protected readonly eventType = "sync" as const;
2116

22-
protected extraContext = {
23-
buildResponse: buildSyncWebhookResponsePayload,
24-
};
25-
2617
constructor(configuration: WebhookConfig<TEvent>) {
2718
super(configuration);
2819

2920
this.event = configuration.event;
3021
}
3122

32-
createHandler(handlerFn: NextJsSyncWebhookHandler<TPayload, TEvent>): NextApiHandler {
23+
createHandler(handlerFn: NextJsSyncWebhookHandler<TPayload>): NextApiHandler {
3324
return super.createHandler(handlerFn);
3425
}
3526
}

src/handlers/shared/saleor-webhook.ts

-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
import { SyncWebhookEventType } from "@/types";
2-
31
import { AuthData } from "../../APL";
4-
import { buildSyncWebhookResponsePayload } from "./sync-webhook-response-builder";
52

63
export const WebhookErrorCodeMap: Record<SaleorWebhookError, number> = {
74
OTHER: 500,
@@ -58,10 +55,6 @@ export type WebhookContext<TPayload> = {
5855
schemaVersion: number | null;
5956
};
6057

61-
export type SyncWebhookInjectedContext<TEvent extends SyncWebhookEventType> = {
62-
buildResponse: typeof buildSyncWebhookResponsePayload<TEvent>;
63-
};
64-
6558
export type FormatWebhookErrorResult = {
6659
code: number;
6760
body: string;

src/handlers/shared/sync-webhook-response-builder.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -167,5 +167,5 @@ export type SyncWebhookResponsesMap = {
167167
* Identity function, but it works on Typescript level to pick right payload based on first param
168168
*/
169169
export const buildSyncWebhookResponsePayload = <E extends SyncWebhookEventType>(
170-
payload: SyncWebhookResponsesMap[E]
170+
payload: SyncWebhookResponsesMap[E],
171171
): SyncWebhookResponsesMap[E] => payload;

0 commit comments

Comments
 (0)