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:
parent
2d28bd5de4
commit
01071866be
|
@ -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,
|
||||
|
|
|
@ -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"');
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue