Add node type definitions for V3 Git plugin (#403)
Summary: This is modeled after the GitHub node module format, with the obvious alterations plus a bit more type safety in the implementation of `toRaw` (namely, we check `type` exhaustively). Test Plan: Unit tests added; run `yarn travis`. wchargin-branch: git-v3-nodes
This commit is contained in:
parent
83151d9fac
commit
7c1b3ca835
|
@ -0,0 +1,80 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`plugins/git/nodes snapshots as expected: blob 1`] = `
|
||||||
|
Object {
|
||||||
|
"address": Array [
|
||||||
|
"sourcecred",
|
||||||
|
"git",
|
||||||
|
"BLOB",
|
||||||
|
"f1f2514ca6d7a6a1a0511957021b1995bf9ace1c",
|
||||||
|
],
|
||||||
|
"structured": Object {
|
||||||
|
"hash": "f1f2514ca6d7a6a1a0511957021b1995bf9ace1c",
|
||||||
|
"type": "BLOB",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`plugins/git/nodes snapshots as expected: commit 1`] = `
|
||||||
|
Object {
|
||||||
|
"address": Array [
|
||||||
|
"sourcecred",
|
||||||
|
"git",
|
||||||
|
"COMMIT",
|
||||||
|
"3715ddfb8d4c4fd2a6f6af75488c82f84c92ec2f",
|
||||||
|
],
|
||||||
|
"structured": Object {
|
||||||
|
"hash": "3715ddfb8d4c4fd2a6f6af75488c82f84c92ec2f",
|
||||||
|
"type": "COMMIT",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`plugins/git/nodes snapshots as expected: submoduleCommit 1`] = `
|
||||||
|
Object {
|
||||||
|
"address": Array [
|
||||||
|
"sourcecred",
|
||||||
|
"git",
|
||||||
|
"SUBMODULE_COMMIT",
|
||||||
|
"https://github.com/sourcecred/example-git-submodule.git",
|
||||||
|
"29ef158bc982733e2ba429fcf73e2f7562244188",
|
||||||
|
],
|
||||||
|
"structured": Object {
|
||||||
|
"commitHash": "29ef158bc982733e2ba429fcf73e2f7562244188",
|
||||||
|
"submoduleUrl": "https://github.com/sourcecred/example-git-submodule.git",
|
||||||
|
"type": "SUBMODULE_COMMIT",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`plugins/git/nodes snapshots as expected: tree 1`] = `
|
||||||
|
Object {
|
||||||
|
"address": Array [
|
||||||
|
"sourcecred",
|
||||||
|
"git",
|
||||||
|
"TREE",
|
||||||
|
"7be3ecfee5314ffa9b2d93fc4377792b2d6d70ed",
|
||||||
|
],
|
||||||
|
"structured": Object {
|
||||||
|
"hash": "7be3ecfee5314ffa9b2d93fc4377792b2d6d70ed",
|
||||||
|
"type": "TREE",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`plugins/git/nodes snapshots as expected: treeEntry 1`] = `
|
||||||
|
Object {
|
||||||
|
"address": Array [
|
||||||
|
"sourcecred",
|
||||||
|
"git",
|
||||||
|
"TREE_ENTRY",
|
||||||
|
"7be3ecfee5314ffa9b2d93fc4377792b2d6d70ed",
|
||||||
|
"science.txt",
|
||||||
|
],
|
||||||
|
"structured": Object {
|
||||||
|
"name": "science.txt",
|
||||||
|
"treeHash": "7be3ecfee5314ffa9b2d93fc4377792b2d6d70ed",
|
||||||
|
"type": "TREE_ENTRY",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
|
@ -0,0 +1,121 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import {NodeAddress, type NodeAddressT} from "../../core/graph";
|
||||||
|
import type {Hash} from "./types";
|
||||||
|
|
||||||
|
export opaque type RawAddress: NodeAddressT = NodeAddressT;
|
||||||
|
|
||||||
|
const GIT_PREFIX = NodeAddress.fromParts(["sourcecred", "git"]);
|
||||||
|
export function _gitAddress(...parts: string[]): RawAddress {
|
||||||
|
return NodeAddress.append(GIT_PREFIX, ...parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BLOB_TYPE: "BLOB" = "BLOB";
|
||||||
|
export const COMMIT_TYPE: "COMMIT" = "COMMIT";
|
||||||
|
export const SUBMODULE_COMMIT_TYPE: "SUBMODULE_COMMIT" = "SUBMODULE_COMMIT";
|
||||||
|
export const TREE_TYPE: "TREE" = "TREE";
|
||||||
|
export const TREE_ENTRY_TYPE: "TREE_ENTRY" = "TREE_ENTRY";
|
||||||
|
|
||||||
|
export const _Prefix = Object.freeze({
|
||||||
|
base: GIT_PREFIX,
|
||||||
|
blob: _gitAddress(BLOB_TYPE),
|
||||||
|
commit: _gitAddress(COMMIT_TYPE),
|
||||||
|
submoduleCommit: _gitAddress(SUBMODULE_COMMIT_TYPE),
|
||||||
|
tree: _gitAddress(TREE_TYPE),
|
||||||
|
treeEntry: _gitAddress(TREE_ENTRY_TYPE),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type BlobAddress = {|
|
||||||
|
+type: typeof BLOB_TYPE,
|
||||||
|
+hash: Hash,
|
||||||
|
|};
|
||||||
|
export type CommitAddress = {|
|
||||||
|
+type: typeof COMMIT_TYPE,
|
||||||
|
+hash: Hash,
|
||||||
|
|};
|
||||||
|
export type SubmoduleCommitAddress = {|
|
||||||
|
+type: typeof SUBMODULE_COMMIT_TYPE,
|
||||||
|
+submoduleUrl: string,
|
||||||
|
+commitHash: Hash,
|
||||||
|
|};
|
||||||
|
export type TreeAddress = {|
|
||||||
|
+type: typeof TREE_TYPE,
|
||||||
|
+hash: Hash,
|
||||||
|
|};
|
||||||
|
export type TreeEntryAddress = {|
|
||||||
|
+type: typeof TREE_ENTRY_TYPE,
|
||||||
|
+treeHash: Hash,
|
||||||
|
+name: string,
|
||||||
|
|};
|
||||||
|
|
||||||
|
export type StructuredAddress =
|
||||||
|
| BlobAddress
|
||||||
|
| CommitAddress
|
||||||
|
| SubmoduleCommitAddress
|
||||||
|
| TreeAddress
|
||||||
|
| TreeEntryAddress;
|
||||||
|
|
||||||
|
export function fromRaw(x: RawAddress): StructuredAddress {
|
||||||
|
function fail() {
|
||||||
|
return new Error(`Bad address: ${NodeAddress.toString(x)}`);
|
||||||
|
}
|
||||||
|
if (!NodeAddress.hasPrefix(x, GIT_PREFIX)) {
|
||||||
|
throw fail();
|
||||||
|
}
|
||||||
|
const [_unused_sc, _unused_git, _type, ...rest] = NodeAddress.toParts(x);
|
||||||
|
const type: $ElementType<StructuredAddress, "type"> = (_type: any);
|
||||||
|
switch (type) {
|
||||||
|
case "BLOB": {
|
||||||
|
if (rest.length !== 1) throw fail();
|
||||||
|
const [hash] = rest;
|
||||||
|
return {type: BLOB_TYPE, hash};
|
||||||
|
}
|
||||||
|
case "COMMIT": {
|
||||||
|
if (rest.length !== 1) throw fail();
|
||||||
|
const [hash] = rest;
|
||||||
|
return {type: COMMIT_TYPE, hash};
|
||||||
|
}
|
||||||
|
case "SUBMODULE_COMMIT": {
|
||||||
|
if (rest.length !== 2) throw fail();
|
||||||
|
const [submoduleUrl, commitHash] = rest;
|
||||||
|
return {type: SUBMODULE_COMMIT_TYPE, submoduleUrl, commitHash};
|
||||||
|
}
|
||||||
|
case "TREE": {
|
||||||
|
if (rest.length !== 1) throw fail();
|
||||||
|
const [hash] = rest;
|
||||||
|
return {type: TREE_TYPE, hash};
|
||||||
|
}
|
||||||
|
case "TREE_ENTRY": {
|
||||||
|
if (rest.length !== 2) throw fail();
|
||||||
|
const [treeHash, name] = rest;
|
||||||
|
return {type: TREE_ENTRY_TYPE, treeHash, name};
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// eslint-disable-next-line no-unused-expressions
|
||||||
|
(type: empty);
|
||||||
|
throw fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toRaw(x: StructuredAddress): RawAddress {
|
||||||
|
switch (x.type) {
|
||||||
|
case BLOB_TYPE:
|
||||||
|
return NodeAddress.append(_Prefix.blob, x.hash);
|
||||||
|
case COMMIT_TYPE:
|
||||||
|
return NodeAddress.append(_Prefix.commit, x.hash);
|
||||||
|
case SUBMODULE_COMMIT_TYPE:
|
||||||
|
return NodeAddress.append(
|
||||||
|
_Prefix.submoduleCommit,
|
||||||
|
x.submoduleUrl,
|
||||||
|
x.commitHash
|
||||||
|
);
|
||||||
|
case TREE_TYPE:
|
||||||
|
return NodeAddress.append(_Prefix.tree, x.hash);
|
||||||
|
case TREE_ENTRY_TYPE:
|
||||||
|
return NodeAddress.append(_Prefix.treeEntry, x.treeHash, x.name);
|
||||||
|
default:
|
||||||
|
// eslint-disable-next-line no-unused-expressions
|
||||||
|
(x.type: empty);
|
||||||
|
throw new Error(`Unexpected type ${x.type}`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,163 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import {NodeAddress} from "../../core/graph";
|
||||||
|
import * as GN from "./nodes";
|
||||||
|
import {fromRaw, toRaw} from "./nodes";
|
||||||
|
|
||||||
|
describe("plugins/git/nodes", () => {
|
||||||
|
const examples = {
|
||||||
|
blob: (): GN.BlobAddress => ({
|
||||||
|
type: GN.BLOB_TYPE,
|
||||||
|
hash: "f1f2514ca6d7a6a1a0511957021b1995bf9ace1c",
|
||||||
|
}),
|
||||||
|
commit: (): GN.CommitAddress => ({
|
||||||
|
type: GN.COMMIT_TYPE,
|
||||||
|
hash: "3715ddfb8d4c4fd2a6f6af75488c82f84c92ec2f",
|
||||||
|
}),
|
||||||
|
submoduleCommit: (): GN.SubmoduleCommitAddress => ({
|
||||||
|
type: GN.SUBMODULE_COMMIT_TYPE,
|
||||||
|
submoduleUrl: "https://github.com/sourcecred/example-git-submodule.git",
|
||||||
|
commitHash: "29ef158bc982733e2ba429fcf73e2f7562244188",
|
||||||
|
}),
|
||||||
|
tree: (): GN.TreeAddress => ({
|
||||||
|
type: GN.TREE_TYPE,
|
||||||
|
hash: "7be3ecfee5314ffa9b2d93fc4377792b2d6d70ed",
|
||||||
|
}),
|
||||||
|
treeEntry: (): GN.TreeEntryAddress => ({
|
||||||
|
type: GN.TREE_ENTRY_TYPE,
|
||||||
|
treeHash: "7be3ecfee5314ffa9b2d93fc4377792b2d6d70ed",
|
||||||
|
name: "science.txt",
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Incorrect types should be caught statically, either due to being
|
||||||
|
// totally invalid...
|
||||||
|
// $ExpectFlowError
|
||||||
|
const _unused_badTree: GN.RepoAddress = {
|
||||||
|
type: "TREEEEE",
|
||||||
|
hash: "browns",
|
||||||
|
};
|
||||||
|
// ...or due to being annotated with the type of a distinct structured
|
||||||
|
// address:
|
||||||
|
// $ExpectFlowError
|
||||||
|
const _unused_badCommit: GN.CommitAddress = {...examples.tree()};
|
||||||
|
|
||||||
|
describe("`fromRaw` after `toRaw` is identity", () => {
|
||||||
|
Object.keys(examples).forEach((example) => {
|
||||||
|
it(example, () => {
|
||||||
|
const instance = examples[example]();
|
||||||
|
expect(fromRaw(toRaw(instance))).toEqual(instance);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("`toRaw` after `fromRaw` is identity", () => {
|
||||||
|
Object.keys(examples).forEach((example) => {
|
||||||
|
it(example, () => {
|
||||||
|
const instance = examples[example]();
|
||||||
|
const raw = toRaw(instance);
|
||||||
|
expect(toRaw(fromRaw(raw))).toEqual(raw);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("snapshots as expected:", () => {
|
||||||
|
Object.keys(examples).forEach((example) => {
|
||||||
|
it(example, () => {
|
||||||
|
const instance = examples[example]();
|
||||||
|
const raw = NodeAddress.toParts(toRaw(instance));
|
||||||
|
expect({address: raw, structured: instance}).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("errors on", () => {
|
||||||
|
describe("fromRaw(...) with", () => {
|
||||||
|
function expectBadAddress(name: string, parts: $ReadOnlyArray<string>) {
|
||||||
|
it(name, () => {
|
||||||
|
const address = GN._gitAddress(...parts);
|
||||||
|
expect(() => fromRaw(address)).toThrow("Bad address");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
it("undefined", () => {
|
||||||
|
// $ExpectFlowError
|
||||||
|
expect(() => fromRaw(undefined)).toThrow("undefined");
|
||||||
|
});
|
||||||
|
it("null", () => {
|
||||||
|
// $ExpectFlowError
|
||||||
|
expect(() => fromRaw(null)).toThrow("null");
|
||||||
|
});
|
||||||
|
it("bad prefix", () => {
|
||||||
|
// $ExpectFlowError
|
||||||
|
expect(() => fromRaw(NodeAddress.fromParts(["foo"]))).toThrow(
|
||||||
|
"Bad address"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
expectBadAddress("no type", []);
|
||||||
|
expectBadAddress("bad type", ["wat"]);
|
||||||
|
|
||||||
|
expectBadAddress("blob with no hash", [GN.BLOB_TYPE]);
|
||||||
|
expectBadAddress("blob with extra field", [
|
||||||
|
GN.BLOB_TYPE,
|
||||||
|
examples.blob().hash,
|
||||||
|
examples.blob().hash,
|
||||||
|
]);
|
||||||
|
|
||||||
|
expectBadAddress("commit with no hash", [GN.COMMIT_TYPE]);
|
||||||
|
expectBadAddress("commit with extra field", [
|
||||||
|
GN.COMMIT_TYPE,
|
||||||
|
examples.commit().hash,
|
||||||
|
examples.commit().hash,
|
||||||
|
]);
|
||||||
|
|
||||||
|
expectBadAddress("tree with no hash", [GN.TREE_TYPE]);
|
||||||
|
expectBadAddress("tree with extra field", [
|
||||||
|
GN.TREE_TYPE,
|
||||||
|
examples.tree().hash,
|
||||||
|
examples.tree().hash,
|
||||||
|
]);
|
||||||
|
|
||||||
|
expectBadAddress("tree entry with no fields", [GN.TREE_ENTRY_TYPE]);
|
||||||
|
expectBadAddress("tree entry with only tree hash", [
|
||||||
|
GN.TREE_ENTRY_TYPE,
|
||||||
|
examples.treeEntry().treeHash,
|
||||||
|
]);
|
||||||
|
expectBadAddress("tree entry with extra field", [
|
||||||
|
GN.TREE_ENTRY_TYPE,
|
||||||
|
examples.treeEntry().treeHash,
|
||||||
|
examples.treeEntry().name,
|
||||||
|
"wat",
|
||||||
|
]);
|
||||||
|
|
||||||
|
expectBadAddress("submodule commit with no fields", [
|
||||||
|
GN.SUBMODULE_COMMIT_TYPE,
|
||||||
|
]);
|
||||||
|
expectBadAddress("submodule commit with only URL", [
|
||||||
|
GN.SUBMODULE_COMMIT_TYPE,
|
||||||
|
examples.submoduleCommit().submoduleUrl,
|
||||||
|
]);
|
||||||
|
expectBadAddress("submodule commit with extra field", [
|
||||||
|
GN.SUBMODULE_COMMIT_TYPE,
|
||||||
|
examples.submoduleCommit().submoduleUrl,
|
||||||
|
examples.submoduleCommit().commitHash,
|
||||||
|
"wat",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("toRaw(...) with", () => {
|
||||||
|
it("null", () => {
|
||||||
|
// $ExpectFlowError
|
||||||
|
expect(() => toRaw(null)).toThrow("null");
|
||||||
|
});
|
||||||
|
it("undefined", () => {
|
||||||
|
// $ExpectFlowError
|
||||||
|
expect(() => toRaw(undefined)).toThrow("undefined");
|
||||||
|
});
|
||||||
|
it("bad type", () => {
|
||||||
|
// $ExpectFlowError
|
||||||
|
expect(() => toRaw({type: "ICE_CREAM"})).toThrow("Unexpected type");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue