Skip to content

Commit

Permalink
"Fix" capnproto serialisation of undefined values, closes #371
Browse files Browse the repository at this point in the history
  • Loading branch information
mrbbot committed Sep 14, 2022
1 parent ddc45f3 commit b0e1d06
Showing 1 changed file with 23 additions and 9 deletions.
32 changes: 23 additions & 9 deletions packages/tre/src/runtime/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,15 @@ function capitalize<S extends string>(str: S): Capitalize<S> {
) as Capitalize<S>;
}

// TODO(important): this will fail if someone sets `{ script: undefined }` or
// something manually, where we're expecting an optional string, need a better
// solution
function encodeCapnpStruct(obj: any, struct: Struct, padding = "") {
// Dynamically encode a capnp struct based on keys and the types of values.
// `obj` should be an instance of a type in `./sserve-conf.ts`. The safety of
// this function relies on getting `./sserve-conf.ts` correct, TypeScript's type
// safety guarantees, and us validating all user input with zod.
//
// TODO: this function is absolutely disgusting, especially the handling of
// `undefined` values. Instead, we should be generating `./sserve-conf.ts`
// and corresponding encoders automatically from the `.capnp` file.
function encodeCapnpStruct(obj: any, struct: Struct) {
const anyStruct = struct as any;
for (const [key, value] of Object.entries(obj)) {
const capitalized = capitalize(key);
Expand All @@ -22,18 +27,27 @@ function encodeCapnpStruct(obj: any, struct: Struct, padding = "") {
const newList: List<any> = anyStruct[`init${capitalized}`](value.length);
for (let i = 0; i < value.length; i++) {
if (typeof value[i] === "object") {
encodeCapnpStruct(value[i], newList.get(i), padding + " ");
encodeCapnpStruct(value[i], newList.get(i));
} else {
newList.set(i, value[i]);
}
}
} else if (typeof value === "object") {
const newStruct: Struct = anyStruct[`init${capitalized}`]();
encodeCapnpStruct(value, newStruct, padding + " ");
encodeCapnpStruct(value, newStruct);
} else {
// TODO: could we catch here if value is actually undefined, but meant to
// be a different type
anyStruct[`set${capitalized}`](value);
const setFunction = `set${capitalized}`;
if (value === undefined) {
// If `value` is `undefined`, we only want to call `set${capitalized}`
// to initialise a union (e.g. `Worker_Binding_Type#setR2Bucket()`).
// If we called a function expecting a parameter (e.g. `Worker#setCompatibilityDate(value)`),
// we'd end up throwing. To differentiate between these cases, we look
// at the function's source code to see if it accepts a parameter.
const setFunctionSrc = anyStruct[setFunction].toString();
const parenIndex = setFunctionSrc.indexOf("(");
if (setFunctionSrc[parenIndex + 1] !== ")") continue; // Skip this key
}
anyStruct[setFunction](value);
}
}
}
Expand Down

0 comments on commit b0e1d06

Please sign in to comment.