mirror of
https://github.com/status-im/sourcecred.git
synced 2025-01-14 14:46:30 +00:00
Add unified address assertions, fromParts, toParts (#357)
Summary: This implements the following functions for the unified addresses: - assertions: `assertValid`, `assertValidParts` - injection: `fromParts` - projection: `toParts` (These functions depend on each other for testing, so we implement them together.) Test Plan: Unit tests included. Run `yarn travis`. wchargin-branch: address-assertion-injection-projection
This commit is contained in:
parent
c1a5e01a2c
commit
32aba15b01
3
src/v3/core/__snapshots__/address.test.js.snap
Normal file
3
src/v3/core/__snapshots__/address.test.js.snap
Normal file
@ -0,0 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`core/address makeAddressModule has a stable internal representation 1`] = `"\\"F\\\\u0000\\\\u0000hello\\\\u0000\\\\u0000\\\\u0000sweet\\\\u0000world\\\\u0000\\\\u0000\\\\u0000\\\\u0000\\""`;
|
@ -115,29 +115,79 @@ export function makeAddressModule(options: Options): AddressModule<string> {
|
||||
otherNoncesWithSeparators.set(otherNonce + separator, otherName);
|
||||
}
|
||||
|
||||
const _ = {name, nonceWithSeparator};
|
||||
|
||||
function assertValid(address: Address, what?: string): void {
|
||||
const _ = {address, what};
|
||||
throw new Error("assertValid");
|
||||
// TODO(perf): If this function becomes a bottleneck, consider
|
||||
// omitting it entirely in production. If this is undesirable, a
|
||||
// number of micro-optimizations can be made.
|
||||
const prefix = what == null ? "" : `${what}: `;
|
||||
if (address == null) {
|
||||
throw new Error(prefix + `expected ${name}, got: ${String(address)}`);
|
||||
}
|
||||
if (!address.endsWith(separator)) {
|
||||
throw new Error(prefix + `expected ${name}, got: ${stringify(address)}`);
|
||||
}
|
||||
if (!address.startsWith(nonceWithSeparator)) {
|
||||
for (const [
|
||||
otherNonceWithSeparator,
|
||||
otherName,
|
||||
] of otherNoncesWithSeparators) {
|
||||
if (address.startsWith(otherNonceWithSeparator)) {
|
||||
throw new Error(
|
||||
prefix + `expected ${name}, got ${otherName}: ${stringify(address)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
throw new Error(prefix + `expected ${name}, got: ${stringify(address)}`);
|
||||
}
|
||||
}
|
||||
|
||||
function partsString(parts: $ReadOnlyArray<string>): string {
|
||||
// This is needed to properly print arrays containing `undefined`.
|
||||
return "[" + parts.map((p) => String(stringify(p))).join(",") + "]";
|
||||
}
|
||||
|
||||
function assertValidParts(
|
||||
parts: $ReadOnlyArray<string>,
|
||||
what?: string
|
||||
): void {
|
||||
const _ = {parts, what};
|
||||
throw new Error("assertValidParts");
|
||||
// TODO(perf): If this function becomes a bottleneck, consider
|
||||
// omitting it entirely in production. If this is undesirable, a
|
||||
// number of micro-optimizations can be made.
|
||||
const prefix = what == null ? "" : `${what}: `;
|
||||
if (parts == null) {
|
||||
throw new Error(
|
||||
prefix + `expected array of parts, got: ${String(parts)}`
|
||||
);
|
||||
}
|
||||
parts.forEach((s: string) => {
|
||||
if (s == null) {
|
||||
throw new Error(
|
||||
prefix +
|
||||
`expected array of parts, got ${String(s)} in: ${partsString(
|
||||
parts
|
||||
)}`
|
||||
);
|
||||
}
|
||||
if (s.indexOf(separator) !== -1) {
|
||||
const where = `${stringify(s)} in ${partsString(parts)}`;
|
||||
throw new Error(prefix + `part contains NUL character: ${where}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function nullDelimited(components: $ReadOnlyArray<string>): string {
|
||||
return [...components, ""].join(separator);
|
||||
}
|
||||
|
||||
function fromParts(parts: $ReadOnlyArray<string>): Address {
|
||||
const _ = parts;
|
||||
throw new Error("fromParts");
|
||||
assertValidParts(parts);
|
||||
return nonce + separator + nullDelimited(parts);
|
||||
}
|
||||
|
||||
function toParts(address: Address): string[] {
|
||||
const _ = address;
|
||||
throw new Error("toParts");
|
||||
assertValid(address);
|
||||
const parts = address.split(separator);
|
||||
return parts.slice(1, parts.length - 1);
|
||||
}
|
||||
|
||||
function toString(address: Address): string {
|
||||
|
@ -58,5 +58,163 @@ describe("core/address", () => {
|
||||
FooAddress.assertValid = FooAddress.assertValid;
|
||||
}).toThrow(/read.only property/);
|
||||
});
|
||||
|
||||
it("has a stable internal representation", () => {
|
||||
const input = ["", "hello", "", "", "sweet", "world", "", "", ""];
|
||||
const address = makeModules().FooAddress.fromParts(input);
|
||||
// We stringify the address here because otherwise literal NUL
|
||||
// characters will appear in the snapshot file.
|
||||
expect(JSON.stringify(address)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("assertValid", () => {
|
||||
const {FooAddress, BarAddress, WatAddress} = makeModules();
|
||||
it("rejects `undefined`", () => {
|
||||
expect(() => {
|
||||
// $ExpectFlowError
|
||||
FooAddress.assertValid(undefined, "widget");
|
||||
}).toThrow("widget: expected FooAddress, got: undefined");
|
||||
});
|
||||
it("rejects `null`", () => {
|
||||
expect(() => {
|
||||
// $ExpectFlowError
|
||||
FooAddress.assertValid(null, "widget");
|
||||
}).toThrow("widget: expected FooAddress, got: null");
|
||||
});
|
||||
it("rejects an address from a known module", () => {
|
||||
const bar = BarAddress.fromParts(["hello"]);
|
||||
expect(() => {
|
||||
FooAddress.assertValid(bar, "widget");
|
||||
}).toThrow("widget: expected FooAddress, got BarAddress:");
|
||||
});
|
||||
it("rejects an address from an unknown module", () => {
|
||||
const wat = WatAddress.fromParts(["hello"]);
|
||||
expect(() => {
|
||||
FooAddress.assertValid(wat, "widget");
|
||||
}).toThrow("widget: expected FooAddress, got:");
|
||||
});
|
||||
it("rejects a string that starts with the nonce but no separator", () => {
|
||||
expect(() => {
|
||||
FooAddress.assertValid("F/things\0", "widget");
|
||||
}).toThrow("widget: expected FooAddress, got:");
|
||||
});
|
||||
it("rejects a string that does not end with a separator", () => {
|
||||
const address = FooAddress.fromParts(["hello"]);
|
||||
const truncated = address.substring(0, address.length - 2);
|
||||
expect(() => {
|
||||
FooAddress.assertValid(truncated, "widget");
|
||||
}).toThrow("widget: expected FooAddress, got:");
|
||||
});
|
||||
it("rejects junk", () => {
|
||||
expect(() => {
|
||||
FooAddress.assertValid("junk", "widget");
|
||||
}).toThrow("widget: expected FooAddress, got:");
|
||||
});
|
||||
});
|
||||
|
||||
describe("assertValidParts", () => {
|
||||
const {FooAddress} = makeModules();
|
||||
it("rejects `undefined`", () => {
|
||||
expect(() => {
|
||||
// $ExpectFlowError
|
||||
FooAddress.assertValidParts(undefined, "widget");
|
||||
}).toThrow("widget: expected array of parts, got: undefined");
|
||||
});
|
||||
it("rejects `null`", () => {
|
||||
expect(() => {
|
||||
// $ExpectFlowError
|
||||
FooAddress.assertValidParts(null, "widget");
|
||||
}).toThrow("widget: expected array of parts, got: null");
|
||||
});
|
||||
it("rejects an array containing `undefined`", () => {
|
||||
expect(() => {
|
||||
// $ExpectFlowError
|
||||
FooAddress.assertValidParts(["hello", undefined, "world"], "widget");
|
||||
}).toThrow(
|
||||
"widget: expected array of parts, got undefined in: " +
|
||||
'["hello",undefined,"world"]'
|
||||
);
|
||||
});
|
||||
it("rejects an array containing `null`", () => {
|
||||
expect(() => {
|
||||
// $ExpectFlowError
|
||||
FooAddress.assertValidParts(["hello", null, "world"], "widget");
|
||||
}).toThrow(
|
||||
"widget: expected array of parts, got null in: " +
|
||||
'["hello",null,"world"]'
|
||||
);
|
||||
});
|
||||
it("rejects an array with a string containing a NUL character", () => {
|
||||
expect(() => {
|
||||
FooAddress.assertValidParts(["hello", "n\0o", "world"], "widget");
|
||||
}).toThrow(
|
||||
"widget: part contains NUL character: " +
|
||||
'"n\\u0000o" in ["hello","n\\u0000o","world"]'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("fromParts", () => {
|
||||
const {FooAddress, BarAddress} = makeModules();
|
||||
|
||||
// We use this next test as a proxy for fully correct validation,
|
||||
// in conjunction with tests on `assertValid` and
|
||||
// `assertValidParts`.
|
||||
it("validates parts", () => {
|
||||
expect(() => {
|
||||
// $ExpectFlowError
|
||||
FooAddress.fromParts(["hello", null, "world"]);
|
||||
}).toThrow(
|
||||
'expected array of parts, got null in: ["hello",null,"world"]'
|
||||
);
|
||||
});
|
||||
|
||||
it("encodes the address kind for the empty address", () => {
|
||||
const foo = FooAddress.fromParts([]);
|
||||
const bar = BarAddress.fromParts([]);
|
||||
expect(foo).not.toEqual(bar);
|
||||
});
|
||||
it("encodes the address kind for a normal address", () => {
|
||||
const foo = FooAddress.fromParts(["hello", "world"]);
|
||||
const bar = BarAddress.fromParts(["hello", "world"]);
|
||||
expect(foo).not.toEqual(bar);
|
||||
});
|
||||
});
|
||||
|
||||
describe("toParts", () => {
|
||||
const {FooAddress, BarAddress} = makeModules();
|
||||
|
||||
// We use this next test as a proxy for fully correct validation,
|
||||
// in conjunction with tests on `assertValid`.
|
||||
it("validates address kind", () => {
|
||||
const bar = BarAddress.fromParts(["hello"]);
|
||||
expect(() => {
|
||||
FooAddress.toParts(bar);
|
||||
}).toThrow("expected FooAddress, got BarAddress:");
|
||||
});
|
||||
|
||||
describe("is a left identity for `fromParts`", () => {
|
||||
function check(description, inputParts) {
|
||||
it(description, () => {
|
||||
const address = FooAddress.fromParts(inputParts);
|
||||
const parts = FooAddress.toParts(address);
|
||||
expect(parts).toEqual(inputParts);
|
||||
});
|
||||
}
|
||||
check("on the empty input", []);
|
||||
check("on an input made of one empty part", [""]);
|
||||
check("on an input made of lots of empty parts", ["", "", ""]);
|
||||
check("on an input with lots of empty parts throughout", [
|
||||
...["", ""],
|
||||
"hello",
|
||||
...["", "", ""],
|
||||
"sweet",
|
||||
"world",
|
||||
...["", "", "", ""],
|
||||
]);
|
||||
check("on a singleton input", ["jar"]);
|
||||
check("on an input with repeated components", ["jar", "jar"]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user