Skip to content

Commit f1b8a1c

Browse files
authoredMay 16, 2023
Support outbounds for dispatch namespace bindings (#3200)
Adds key "outbound" to the dispatch_namespaces binding section Additionally, outbounds can declare params (string[]) that will be forwarded from the dispatcher to the outbound worker

File tree

9 files changed

+331
-5
lines changed

9 files changed

+331
-5
lines changed
 

‎.changeset/empty-kangaroos-sip.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"wrangler": minor
3+
---
4+
5+
Support outbounds for dispatch_namespace bindings

‎packages/wrangler/src/__tests__/configuration.test.ts

+107
Original file line numberDiff line numberDiff line change
@@ -2399,6 +2399,113 @@ describe("normalizeAndValidateConfig()", () => {
23992399
- \\"dispatch_namespaces[5]\\" should have a string \\"binding\\" field but got {\\"binding\\":123,\\"namespace\\":\\"DISPATCH_NAMESPACE_BINDING_SERVICE_1\\"}.
24002400
- \\"dispatch_namespaces[6]\\" should have a string \\"binding\\" field but got {\\"binding\\":123,\\"service\\":456}.
24012401
- \\"dispatch_namespaces[6]\\" should have a string \\"namespace\\" field but got {\\"binding\\":123,\\"service\\":456}."
2402+
`);
2403+
});
2404+
2405+
test("should error on invalid outbounds for a namespace", () => {
2406+
const { diagnostics } = normalizeAndValidateConfig(
2407+
{
2408+
dispatch_namespaces: [
2409+
{
2410+
binding: "DISPATCH_NAMESPACE_BINDING_1",
2411+
namespace: "NAMESPACE",
2412+
outbound: "a string",
2413+
},
2414+
{
2415+
binding: "DISPATCH_NAMESPACE_BINDING_2",
2416+
namespace: "NAMESPACE",
2417+
outbound: [{ not: "valid" }],
2418+
},
2419+
{
2420+
binding: "DISPATCH_NAMESPACE_BINDING_3",
2421+
namespace: "NAMESPACE",
2422+
outbound: {
2423+
service: 123,
2424+
},
2425+
},
2426+
{
2427+
binding: "DISPATCH_NAMESPACE_BINDING_4",
2428+
namespace: "NAMESPACE",
2429+
outbound: {
2430+
service: "outbound",
2431+
environment: { bad: "env" },
2432+
},
2433+
},
2434+
{
2435+
binding: "DISPATCH_NAMESPACE_BINDING_5",
2436+
namespace: "NAMESPACE",
2437+
outbound: {
2438+
environment: "production",
2439+
},
2440+
},
2441+
{
2442+
binding: "DISPATCH_NAMESPACE_BINDING_6",
2443+
namespace: "NAMESPACE",
2444+
outbound: {
2445+
service: "outbound",
2446+
parameters: "bad",
2447+
},
2448+
},
2449+
{
2450+
binding: "DISPATCH_NAMESPACE_BINDING_7",
2451+
namespace: "NAMESPACE",
2452+
outbound: {
2453+
service: "outbound",
2454+
parameters: false,
2455+
},
2456+
},
2457+
{
2458+
binding: "DISPATCH_NAMESPACE_BINDING_8",
2459+
namespace: "NAMESPACE",
2460+
outbound: {
2461+
service: "outbound",
2462+
parameters: [true, { not: "good" }],
2463+
},
2464+
},
2465+
// these are correct
2466+
{
2467+
binding: "DISPATCH_NAMESPACE_BINDING_9",
2468+
namespace: "NAMESPACE",
2469+
outbound: {
2470+
service: "outbound",
2471+
parameters: ["finally", "real", "params"],
2472+
},
2473+
},
2474+
{
2475+
binding: "DISPATCH_NAMESPACE_BINDING_10",
2476+
namespace: "NAMESPACE",
2477+
outbound: {
2478+
service: "outbound",
2479+
environment: "production",
2480+
parameters: ["some", "more", "params"],
2481+
},
2482+
},
2483+
],
2484+
} as unknown as RawConfig,
2485+
undefined,
2486+
{ env: undefined }
2487+
);
2488+
expect(diagnostics.hasWarnings()).toBe(false);
2489+
expect(diagnostics.hasErrors()).toBe(true);
2490+
expect(diagnostics.renderErrors()).toMatchInlineSnapshot(`
2491+
"Processing wrangler configuration:
2492+
- \\"dispatch_namespaces[0].outbound\\" should be an object, but got \\"a string\\"
2493+
- \\"dispatch_namespaces[0]\\" has an invalid outbound definition.
2494+
- \\"dispatch_namespaces[1].outbound.service\\" is a required field.
2495+
- \\"dispatch_namespaces[1]\\" has an invalid outbound definition.
2496+
- Expected \\"dispatch_namespaces[2].outbound.service\\" to be of type string but got 123.
2497+
- \\"dispatch_namespaces[2]\\" has an invalid outbound definition.
2498+
- Expected \\"dispatch_namespaces[3].outbound.environment\\" to be of type string but got {\\"bad\\":\\"env\\"}.
2499+
- \\"dispatch_namespaces[3]\\" has an invalid outbound definition.
2500+
- \\"dispatch_namespaces[4].outbound.service\\" is a required field.
2501+
- \\"dispatch_namespaces[4]\\" has an invalid outbound definition.
2502+
- Expected \\"dispatch_namespaces[5].outbound.parameters\\" to be an array of strings but got \\"bad\\"
2503+
- \\"dispatch_namespaces[5]\\" has an invalid outbound definition.
2504+
- Expected \\"dispatch_namespaces[6].outbound.parameters\\" to be an array of strings but got false
2505+
- \\"dispatch_namespaces[6]\\" has an invalid outbound definition.
2506+
- Expected \\"dispatch_namespaces[7].outbound.parameters.[0]\\" to be of type string but got true.
2507+
- Expected \\"dispatch_namespaces[7].outbound.parameters.[1]\\" to be of type string but got {\\"not\\":\\"good\\"}.
2508+
- \\"dispatch_namespaces[7]\\" has an invalid outbound definition."
24022509
`);
24032510
});
24042511
});

‎packages/wrangler/src/__tests__/publish.test.ts

+107
Original file line numberDiff line numberDiff line change
@@ -6492,6 +6492,113 @@ addEventListener('fetch', event => {});`
64926492
Published test-name (TIMINGS)
64936493
https://test-name.test-sub-domain.workers.dev
64946494
Current Deployment ID: Galaxy-Class"
6495+
`);
6496+
expect(std.err).toMatchInlineSnapshot(`""`);
6497+
expect(std.warn).toMatchInlineSnapshot(`""`);
6498+
});
6499+
6500+
it("should support dispatch namespace bindings with an outbound worker", async () => {
6501+
writeWranglerToml({
6502+
dispatch_namespaces: [
6503+
{
6504+
binding: "foo",
6505+
namespace: "Foo",
6506+
outbound: { service: "foo_outbound" },
6507+
},
6508+
{
6509+
binding: "bar",
6510+
namespace: "Bar",
6511+
outbound: { service: "bar_outbound", environment: "production" },
6512+
},
6513+
],
6514+
});
6515+
writeWorkerSource();
6516+
mockSubDomainRequest();
6517+
mockUploadWorkerRequest({
6518+
expectedBindings: [
6519+
{
6520+
type: "dispatch_namespace",
6521+
name: "foo",
6522+
namespace: "Foo",
6523+
outbound: {
6524+
worker: {
6525+
service: "foo_outbound",
6526+
},
6527+
},
6528+
},
6529+
{
6530+
type: "dispatch_namespace",
6531+
name: "bar",
6532+
namespace: "Bar",
6533+
outbound: {
6534+
worker: {
6535+
service: "bar_outbound",
6536+
environment: "production",
6537+
},
6538+
},
6539+
},
6540+
],
6541+
});
6542+
await runWrangler("publish index.js");
6543+
expect(std.out).toMatchInlineSnapshot(`
6544+
"Total Upload: xx KiB / gzip: xx KiB
6545+
Your worker has access to the following bindings:
6546+
- dispatch namespaces:
6547+
- foo: Foo (outbound -> foo_outbound)
6548+
- bar: Bar (outbound -> bar_outbound)
6549+
Uploaded test-name (TIMINGS)
6550+
Published test-name (TIMINGS)
6551+
https://test-name.test-sub-domain.workers.dev
6552+
Current Deployment ID: Galaxy-Class"
6553+
`);
6554+
expect(std.err).toMatchInlineSnapshot(`""`);
6555+
expect(std.warn).toMatchInlineSnapshot(`""`);
6556+
});
6557+
6558+
it("should support dispatch namespace bindings with parameterized outbounds", async () => {
6559+
writeWranglerToml({
6560+
dispatch_namespaces: [
6561+
{
6562+
binding: "foo",
6563+
namespace: "Foo",
6564+
outbound: {
6565+
service: "foo_outbound",
6566+
parameters: ["some", "outbound", "params"],
6567+
},
6568+
},
6569+
],
6570+
});
6571+
writeWorkerSource();
6572+
mockSubDomainRequest();
6573+
mockUploadWorkerRequest({
6574+
expectedBindings: [
6575+
{
6576+
type: "dispatch_namespace",
6577+
name: "foo",
6578+
namespace: "Foo",
6579+
outbound: {
6580+
worker: {
6581+
service: "foo_outbound",
6582+
},
6583+
params: [
6584+
{ name: "some" },
6585+
{ name: "outbound" },
6586+
{ name: "params" },
6587+
],
6588+
},
6589+
},
6590+
],
6591+
});
6592+
await runWrangler("publish index.js");
6593+
expect(std.out).toMatchInlineSnapshot(`
6594+
"Total Upload: xx KiB / gzip: xx KiB
6595+
Your worker has access to the following bindings:
6596+
- dispatch namespaces:
6597+
- foo: Foo (outbound -> foo_outbound)
6598+
Uploaded test-name (TIMINGS)
6599+
Published test-name (TIMINGS)
6600+
https://test-name.test-sub-domain.workers.dev
6601+
Current Deployment ID: Galaxy-Class"
64956602
`);
64966603
expect(std.err).toMatchInlineSnapshot(`""`);
64976604
expect(std.warn).toMatchInlineSnapshot(`""`);

‎packages/wrangler/src/config/environment.ts

+11
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,8 @@ interface EnvironmentInheritable {
221221
binding: string;
222222
/** The namespace to bind to. */
223223
namespace: string;
224+
/** Details about the outbound worker which will handle outbound requests from your namespace */
225+
outbound?: DispatchNamespaceOutbound;
224226
}[];
225227

226228
/**
@@ -657,3 +659,12 @@ export type TailConsumer = {
657659
/** (Optional) The environt of the service. */
658660
environment?: string;
659661
};
662+
663+
export interface DispatchNamespaceOutbound {
664+
/** Name of the service handling the outbound requests */
665+
service: string;
666+
/** (Optional) Name of the environment handling the outbound requests. */
667+
environment?: string;
668+
/** (Optional) List of parameter names, for sending context from your dispatch worker to the outbound handler */
669+
parameters?: string[];
670+
}

‎packages/wrangler/src/config/index.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -337,10 +337,12 @@ export function printBindings(bindings: CfWorkerInit["bindings"]) {
337337
if (dispatch_namespaces !== undefined && dispatch_namespaces.length > 0) {
338338
output.push({
339339
type: "dispatch namespaces",
340-
entries: dispatch_namespaces.map(({ binding, namespace }) => {
340+
entries: dispatch_namespaces.map(({ binding, namespace, outbound }) => {
341341
return {
342342
key: binding,
343-
value: namespace,
343+
value: outbound
344+
? `${namespace} (outbound -> ${outbound.service})`
345+
: namespace,
344346
};
345347
}),
346348
});

‎packages/wrangler/src/config/validation.ts

+58
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import type {
3434
Environment,
3535
Rule,
3636
TailConsumer,
37+
DispatchNamespaceOutbound,
3738
} from "./environment";
3839
import type { ValidatorFn } from "./validation-helpers";
3940

@@ -2368,9 +2369,66 @@ const validateWorkerNamespaceBinding: ValidatorFn = (
23682369
);
23692370
isValid = false;
23702371
}
2372+
if (hasProperty(value, "outbound")) {
2373+
if (
2374+
!validateWorkerNamespaceOutbound(
2375+
diagnostics,
2376+
`${field}.outbound`,
2377+
value.outbound ?? {}
2378+
)
2379+
) {
2380+
diagnostics.errors.push(`"${field}" has an invalid outbound definition.`);
2381+
isValid = false;
2382+
}
2383+
}
23712384
return isValid;
23722385
};
23732386

2387+
function validateWorkerNamespaceOutbound(
2388+
diagnostics: Diagnostics,
2389+
field: string,
2390+
value: DispatchNamespaceOutbound
2391+
): boolean {
2392+
if (typeof value !== "object" || value === null) {
2393+
diagnostics.errors.push(
2394+
`"${field}" should be an object, but got ${JSON.stringify(value)}`
2395+
);
2396+
return false;
2397+
}
2398+
2399+
let isValid = true;
2400+
2401+
// Namespace outbounds need at least a service name
2402+
isValid =
2403+
isValid &&
2404+
validateRequiredProperty(
2405+
diagnostics,
2406+
field,
2407+
"service",
2408+
value.service,
2409+
"string"
2410+
);
2411+
isValid =
2412+
isValid &&
2413+
validateOptionalProperty(
2414+
diagnostics,
2415+
field,
2416+
"environment",
2417+
value.environment,
2418+
"string"
2419+
);
2420+
isValid =
2421+
isValid &&
2422+
validateOptionalTypedArray(
2423+
diagnostics,
2424+
`${field}.parameters`,
2425+
value.parameters,
2426+
"string"
2427+
);
2428+
2429+
return isValid;
2430+
}
2431+
23742432
const validateMTlsCertificateBinding: ValidatorFn = (
23752433
diagnostics,
23762434
field,

‎packages/wrangler/src/create-worker-upload-form.ts

+22-2
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,18 @@ export type WorkerMetadataBinding =
5454
| { type: "constellation"; name: string; project: string }
5555
| { type: "service"; name: string; service: string; environment?: string }
5656
| { type: "analytics_engine"; name: string; dataset?: string }
57-
| { type: "dispatch_namespace"; name: string; namespace: string }
57+
| {
58+
type: "dispatch_namespace";
59+
name: string;
60+
namespace: string;
61+
outbound?: {
62+
worker: {
63+
service: string;
64+
environment?: string;
65+
};
66+
params?: { name: string }[];
67+
};
68+
}
5869
| { type: "mtls_certificate"; name: string; certificate_id: string }
5970
| {
6071
type: "logfwdr";
@@ -194,11 +205,20 @@ export function createWorkerUploadForm(worker: CfWorkerInit): FormData {
194205
});
195206
});
196207

197-
bindings.dispatch_namespaces?.forEach(({ binding, namespace }) => {
208+
bindings.dispatch_namespaces?.forEach(({ binding, namespace, outbound }) => {
198209
metadataBindings.push({
199210
name: binding,
200211
type: "dispatch_namespace",
201212
namespace,
213+
...(outbound && {
214+
outbound: {
215+
worker: {
216+
service: outbound.service,
217+
environment: outbound.environment,
218+
},
219+
params: outbound.parameters?.map((p) => ({ name: p })),
220+
},
221+
}),
202222
});
203223
});
204224

‎packages/wrangler/src/init.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -1010,7 +1010,18 @@ export function mapBindings(bindings: WorkerMetadataBinding[]): RawConfig {
10101010
{
10111011
configObj.dispatch_namespaces = [
10121012
...(configObj.dispatch_namespaces ?? []),
1013-
{ binding: binding.name, namespace: binding.namespace },
1013+
{
1014+
binding: binding.name,
1015+
namespace: binding.namespace,
1016+
...(binding.outbound && {
1017+
outbound: {
1018+
service: binding.outbound.worker.service,
1019+
environment: binding.outbound.worker.environment,
1020+
parameters:
1021+
binding.outbound.params?.map((p) => p.name) ?? [],
1022+
},
1023+
}),
1024+
},
10141025
];
10151026
}
10161027
break;

‎packages/wrangler/src/worker.ts

+5
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,11 @@ interface CfAnalyticsEngineDataset {
174174
interface CfDispatchNamespace {
175175
binding: string;
176176
namespace: string;
177+
outbound?: {
178+
service: string;
179+
environment?: string;
180+
parameters?: string[];
181+
};
177182
}
178183

179184
interface CfMTlsCertificate {

0 commit comments

Comments
 (0)
Please sign in to comment.