Add `GitState`, `Environment` types to `version` (#691)

Summary:
These types will shortly be added to the global `VersionInfo`. For now,
we include the types and validation logic only.

Test Plan:
Unit tests suffice.

wchargin-branch: add-rich-version-types
This commit is contained in:
William Chargin 2018-08-16 13:28:29 -07:00 committed by GitHub
parent 2d28bd5de4
commit 01071866be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 155 additions and 0 deletions

View File

@ -5,6 +5,52 @@ export type VersionInfo = {|
+minor: number,
+patch: number,
|};
export type GitState = {|
+commitHash: string,
+commitTimestamp: string, // YYYYmmdd-HHMM, in commit-local time
+dirty: boolean, // does the worktree have unstaged/uncommitted changes?
|};
export type Environment = "development" | "production" | "test";
/**
* Parse the given string as a `GitState`, throwing an error if it is
* not valid. The argument should be the result of calling
* `JSON.stringify` with a valid `GitState`. Thus, this is a checked
* version of `JSON.parse`.
*/
export function parseGitState(raw: ?string): GitState {
if (typeof raw !== "string") {
throw new Error("gitState: not a string: " + String(raw));
}
const parsed: mixed = Object.freeze(JSON.parse(raw));
if (parsed == null || typeof parsed !== "object") {
throw new Error("gitState: not a JSON object: " + String(parsed));
}
// This intermediate variable helps out Flow's inference...
const gitState: Object = parsed;
if (
typeof gitState.commitHash !== "string" ||
typeof gitState.commitTimestamp !== "string" ||
typeof gitState.dirty !== "boolean" ||
Object.keys(gitState).length !== 3
) {
throw new Error("gitState: bad shape: " + JSON.stringify(gitState));
}
return gitState;
}
/**
* Parse the given string as an `Environment`, throwing an error if it
* is not valid. The input should be a valid `Environment`.
*/
export function parseEnvironment(raw: ?string): Environment {
if (raw !== "development" && raw !== "production" && raw !== "test") {
throw new Error(
"environment: " + (raw == null ? String(raw) : JSON.stringify(raw))
);
}
return raw;
}
export const VERSION_INFO = Object.freeze({
major: 0,

109
src/app/version.test.js Normal file
View File

@ -0,0 +1,109 @@
// @flow
import {type Environment, parseEnvironment, parseGitState} from "./version";
describe("app/version", () => {
// Like `VersionInfo`, but with some extra properties that will
// shortly be added to that type.
const version = () => ({
major: 3,
minor: 13,
patch: 37,
gitState: {
commitHash: "d0e1a2d3b4e5",
commitTimestamp: "20010203-0405",
dirty: true,
},
environment: "test",
});
describe("parseGitState", () => {
it("fails given literal `undefined`", () => {
expect(() => parseGitState(undefined)).toThrow(
"gitState: not a string: undefined"
);
});
it("fails given literal `null`", () => {
expect(() => parseGitState(null)).toThrow("gitState: not a string: null");
});
it("fails given JSON `null`", () => {
expect(() => parseGitState("null")).toThrow(
"gitState: not a JSON object: null"
);
});
it("fails given invalid JSON", () => {
expect(() => parseGitState("wat")).toThrow(
"Unexpected token w in JSON at position 0"
);
});
it("fails given a JSON string", () => {
expect(() => parseGitState(JSON.stringify("wat"))).toThrow(
"gitState: not a JSON object: wat"
);
});
it("fails given a non-stringified `GitState`", () => {
// $ExpectFlowError
expect(() => parseGitState(version().gitState)).toThrow(
"gitState: not a string: [object Object]"
);
});
it("fails given a JSON object missing a property", () => {
const gitState = version().gitState;
delete gitState.dirty;
expect(() => parseGitState(JSON.stringify(gitState))).toThrow(
"gitState: bad shape: {"
);
});
function expectBadShape(gitState) {
expect(() => parseGitState(JSON.stringify(gitState))).toThrow(
"gitState: bad shape: {"
);
}
it("fails given a JSON object with an extra property", () => {
expectBadShape({...version().gitState, wat: "wot"});
});
it("fails given a JSON object with bad `commitHash`", () => {
expectBadShape({...version().gitState, commitHash: true});
expectBadShape({...version().gitState, commitHash: 27});
expectBadShape({...version().gitState, commitHash: null});
});
it("fails given a JSON object with bad `commitTimestamp`", () => {
expectBadShape({...version().gitState, commitTimestamp: true});
expectBadShape({...version().gitState, commitTimestamp: 27});
expectBadShape({...version().gitState, commitTimestamp: null});
});
it("fails given a JSON object with bad `dirty`", () => {
expectBadShape({...version().gitState, dirty: "true"});
expectBadShape({...version().gitState, dirty: 27});
expectBadShape({...version().gitState, dirty: null});
});
it("parses a valid `GitState`", () => {
const gitState = version().gitState;
expect(parseGitState(JSON.stringify(gitState))).toEqual(gitState);
});
});
describe("parseEnvironment", () => {
it("parses each of the valid environments", () => {
const allEnvs = {development: true, production: true, test: true};
function _unused_staticCheck(x: Environment): true {
return allEnvs[x];
}
for (const env of Object.keys(allEnvs)) {
expect(parseEnvironment(env)).toEqual(env);
}
});
it("fails given literal `undefined`", () => {
expect(() => parseEnvironment(undefined)).toThrow(
"environment: undefined"
);
});
it("fails given literal `null`", () => {
expect(() => parseEnvironment(null)).toThrow("environment: null");
});
it("fails given a non-environment string", () => {
expect(() => parseEnvironment("wat")).toThrow('environment: "wat"');
});
});
});