Add safe type coercion for GitHub api (#173)
In general, methods in the porcelain GitHub api may return multiple types; e.g. a reference could be to an Issue, PullRequest, Comment, Author (or more). To make working with the api more convenient while maintaining safety, this commit adds a static `asType` method to each Entity class, which confirms that type coercion is safe, and errors if not. This commit also adds `issueOrPRByNumber`, a convenience method, to api.test.js. Test plan: Check the API usage and verify that it is reasonable.
This commit is contained in:
parent
d878be6550
commit
22ca77ed05
|
@ -1,5 +1,7 @@
|
|||
// @flow
|
||||
|
||||
import stringify from "json-stable-stringify";
|
||||
|
||||
import {Graph} from "../../core/graph";
|
||||
import type {Node} from "../../core/graph";
|
||||
import type {Address} from "../../core/address";
|
||||
|
@ -23,6 +25,16 @@ import {
|
|||
PULL_REQUEST_NODE_TYPE,
|
||||
} from "./types";
|
||||
|
||||
export type Entity = Issue | PullRequest | Comment | Author;
|
||||
|
||||
function assertEntityType(e: Entity, t: NodeType) {
|
||||
if (e.type() !== t) {
|
||||
throw new Error(
|
||||
`Expected entity at ${stringify(e.address())} to have type ${t}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class Repository {
|
||||
repositoryName: string;
|
||||
graph: Graph<NodePayload, EdgePayload>;
|
||||
|
@ -123,6 +135,10 @@ class Commentable<T: IssueNodePayload | PullRequestNodePayload> extends Post<
|
|||
}
|
||||
|
||||
export class Author extends GithubEntity<AuthorNodePayload> {
|
||||
static from(e: Entity): Author {
|
||||
assertEntityType(e, AUTHOR_NODE_TYPE);
|
||||
return (e: any);
|
||||
}
|
||||
login(): string {
|
||||
return this.node().payload.login;
|
||||
}
|
||||
|
@ -133,6 +149,10 @@ export class Author extends GithubEntity<AuthorNodePayload> {
|
|||
}
|
||||
|
||||
export class PullRequest extends Commentable<PullRequestNodePayload> {
|
||||
static from(e: Entity): PullRequest {
|
||||
assertEntityType(e, PULL_REQUEST_NODE_TYPE);
|
||||
return (e: any);
|
||||
}
|
||||
number(): number {
|
||||
return this.node().payload.number;
|
||||
}
|
||||
|
@ -142,6 +162,10 @@ export class PullRequest extends Commentable<PullRequestNodePayload> {
|
|||
}
|
||||
|
||||
export class Issue extends Commentable<IssueNodePayload> {
|
||||
static from(e: Entity): Issue {
|
||||
assertEntityType(e, ISSUE_NODE_TYPE);
|
||||
return (e: any);
|
||||
}
|
||||
number(): number {
|
||||
return this.node().payload.number;
|
||||
}
|
||||
|
@ -150,4 +174,9 @@ export class Issue extends Commentable<IssueNodePayload> {
|
|||
}
|
||||
}
|
||||
|
||||
export class Comment extends Post<CommentNodePayload> {}
|
||||
export class Comment extends Post<CommentNodePayload> {
|
||||
static from(e: Entity): Comment {
|
||||
assertEntityType(e, COMMENT_NODE_TYPE);
|
||||
return (e: any);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,12 +12,16 @@ import {
|
|||
describe("GitHub porcelain API", () => {
|
||||
const graph = parse("sourcecred/example-repo", exampleRepoData);
|
||||
const repo = new Repository("sourcecred/example-repo", graph);
|
||||
function issueOrPRByNumber(n: number): Issue | PullRequest {
|
||||
const result = repo.issueOrPRByNumber(n);
|
||||
if (result == null) {
|
||||
throw new Error(`Expected Issue/PR ${n} to exist`);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
it("Issue", () => {
|
||||
const issue = repo.issueOrPRByNumber(1);
|
||||
if (issue == null) {
|
||||
throw new Error("Issue reaching issue!");
|
||||
}
|
||||
const issue = issueOrPRByNumber(1);
|
||||
expect(issue.title()).toBe("An example issue.");
|
||||
expect(issue.body()).toBe("This is just an example issue.");
|
||||
expect(issue.number()).toBe(1);
|
||||
|
@ -31,10 +35,7 @@ describe("GitHub porcelain API", () => {
|
|||
});
|
||||
|
||||
it("PullRequest", () => {
|
||||
const pullRequest = repo.issueOrPRByNumber(3);
|
||||
if (pullRequest == null) {
|
||||
throw new Error("Issue reaching PR!");
|
||||
}
|
||||
const pullRequest = issueOrPRByNumber(3);
|
||||
expect(pullRequest.body()).toBe("Oh look, it's a pull request.");
|
||||
expect(pullRequest.url()).toBe(
|
||||
"https://github.com/sourcecred/example-repo/pull/3"
|
||||
|
@ -46,10 +47,7 @@ describe("GitHub porcelain API", () => {
|
|||
});
|
||||
|
||||
it("Comment", () => {
|
||||
const issue = repo.issueOrPRByNumber(6);
|
||||
if (issue == null) {
|
||||
throw new Error("Issue reaching issue!");
|
||||
}
|
||||
const issue = issueOrPRByNumber(6);
|
||||
const comments = issue.comments();
|
||||
expect(comments.length).toMatchSnapshot();
|
||||
const comment = comments[0];
|
||||
|
@ -78,4 +76,27 @@ describe("GitHub porcelain API", () => {
|
|||
expect(decentralion.node()).toMatchSnapshot();
|
||||
expect(decentralion.address()).toEqual(decentralion.node().address);
|
||||
});
|
||||
|
||||
describe("type coercion", () => {
|
||||
it("type coercion works when typed correctly", () => {
|
||||
const issue: Issue = Issue.from(issueOrPRByNumber(1));
|
||||
const pr: PullRequest = PullRequest.from(issueOrPRByNumber(3));
|
||||
const author: Author = Author.from(issueOrPRByNumber(3).authors()[0]);
|
||||
const comment: Comment = Comment.from(issueOrPRByNumber(2).comments()[0]);
|
||||
});
|
||||
it("type coercion throws error when typed incorrectly", () => {
|
||||
expect(() => PullRequest.from(issueOrPRByNumber(1))).toThrowError(
|
||||
"to have type PULL_REQUEST"
|
||||
);
|
||||
expect(() => Issue.from(issueOrPRByNumber(3))).toThrowError(
|
||||
"to have type ISSUE"
|
||||
);
|
||||
expect(() =>
|
||||
Comment.from(issueOrPRByNumber(3).authors()[0])
|
||||
).toThrowError("to have type COMMENT");
|
||||
expect(() =>
|
||||
Author.from(issueOrPRByNumber(2).comments()[0])
|
||||
).toThrowError("to have type AUTHOR");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue