Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes encoding of circular referenced object / complex object #1525

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
15 changes: 15 additions & 0 deletions spec/common/providers/https.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -924,6 +924,21 @@ describe("encoding/decoding", () => {
});
});

it("Throws object with self reference", () => {
class TestClass {
foo: string;
bar: number;
self: TestClass;
constructor(foo: string, bar: number) {
this.foo = foo;
this.bar = bar;
this.self = this;
}
}
const testObject = new TestClass("hello", 1);
expect(()=>https.encode(testObject)).to.throw(`Data cannot be encoded in JSON: ${testObject}`);
});

it("encodes function as an empty object", () => {
expect(https.encode(() => "foo")).to.deep.equal({});
});
Expand Down
44 changes: 32 additions & 12 deletions src/common/providers/https.ts
Original file line number Diff line number Diff line change
Expand Up @@ -404,12 +404,14 @@ const LONG_TYPE = "type.googleapis.com/google.protobuf.Int64Value";
/** @hidden */
const UNSIGNED_LONG_TYPE = "type.googleapis.com/google.protobuf.UInt64Value";

/** @hidden */
const SELF_REFERENCE_WEAKSET = new WeakSet<object|((...args:any[])=>any)>();
/**
* Encodes arbitrary data in our special format for JSON.
* This is exposed only for testing.
*/
/** @hidden */
export function encode(data: any): any {
export function encode(data: unknown): any {
if (data === null || typeof data === "undefined") {
return null;
}
Expand All @@ -430,18 +432,36 @@ export function encode(data: any): any {
if (Array.isArray(data)) {
return data.map(encode);
}
if (typeof data === "object" || typeof data === "function") {
// Sadly we don't have Object.fromEntries in Node 10, so we can't use a single
// list comprehension
const obj: Record<string, any> = {};
for (const [k, v] of Object.entries(data)) {
obj[k] = encode(v);
}
return obj;
if(!isFunctionOrObject(data)||SELF_REFERENCE_WEAKSET.has(data)) {
logger.error("Data cannot be encoded in JSON.", data);
throw new Error(`Data cannot be encoded in JSON: ${data}`);
}

// Sadly we don't have Object.fromEntries in Node 10, so we can't use a single
// list comprehension
const obj: Record<string, any> = {};

SELF_REFERENCE_WEAKSET.add(data);

for (const [k, v] of Object.entries(data)) {
obj[k] = encode(v);
}
// If we got this far, the data is not encodable.
logger.error("Data cannot be encoded in JSON.", data);
throw new Error(`Data cannot be encoded in JSON: ${data}`);

// clean after recursive call -
// we don't want to keep references to objects that are not part of the current object
SELF_REFERENCE_WEAKSET.delete(data);

return obj;
}

function isFunctionOrObject(data: unknown): data is object|((...args:any[])=>any) {
const isObjectOrFunction = typeof data === "object" || typeof data === "function";

if (!isObjectOrFunction) {
// If we got this far, the data is not encodable.
return false;
}
return true;
}

/**
Expand Down