From 24a3f2d11e4359838a5b37d0f71fe81c8f4e1d85 Mon Sep 17 00:00:00 2001 From: Kir_Antipov Date: Mon, 6 Jun 2022 18:09:45 +0300 Subject: [PATCH] Implemented a simpler way to process input data --- src/index.ts | 6 +- src/utils/input-utils.ts | 49 ++++++++++++ test/input-utils.test.ts | 164 ++++++++++++++++++++++++++++++++++----- 3 files changed, 196 insertions(+), 23 deletions(-) diff --git a/src/index.ts b/src/index.ts index a2ad172..cb1cde4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ import { getRequiredFiles, gradleOutputSelector } from "./utils/file-utils"; import PublisherFactory from "./publishing/publisher-factory"; import PublisherTarget from "./publishing/publisher-target"; -import { getInputAsObject } from "./utils/input-utils"; +import { getInputAsObject, mapNumberInput } from "./utils/input-utils"; import { getDefaultLogger } from "./utils/logger-utils"; import { retry } from "./utils/function-utils"; @@ -21,8 +21,8 @@ async function main() { const options = { ...commonOptions, ...publisherOptions }; const fileSelector = options.files && (typeof(options.files) === "string" || options.files.primary) ? options.files : gradleOutputSelector; const files = await getRequiredFiles(fileSelector); - const retryAttempts = +options.retry?.["attempts"] || 0; - const retryDelay = +options.retry?.["delay"] || 0; + const retryAttempts = mapNumberInput(options.retry?.["attempts"]); + const retryDelay = mapNumberInput(options.retry?.["delay"]); const publisher = publisherFactory.create(target, logger); logger.info(`Publishing assets to ${targetName}...`); diff --git a/src/utils/input-utils.ts b/src/utils/input-utils.ts index 2494942..18ab1bf 100644 --- a/src/utils/input-utils.ts +++ b/src/utils/input-utils.ts @@ -32,3 +32,52 @@ function init(root: InputObject, path: string[], value: string): void { init(inner, path.slice(1), value); } } + +export function mapStringInput(value: any, defaultValue = ""): string { + return mapInput(value, defaultValue ?? ""); +} + +export function mapObjectInput(value: any, defaultValue: object = null): object { + return mapInput(value, defaultValue ?? null); +} + +export function mapNumberInput(value: any, defaultValue = 0): number { + return mapInput(value, defaultValue ?? 0, { + string: x => { + const num = +x; + return isNaN(num) ? undefined : num; + } + }); +} + +export function mapBooleanInput(value: any, defaultValue = false): boolean { + return mapInput(value, defaultValue ?? false, { + string: x => { + const strValue = x.trim().toLowerCase(); + return ( + strValue === "true" ? true : + strValue === "false" ? false : + undefined + ); + } + }); +} + +export function mapInput(value: any, fallbackValue: T, mappers?: Record T | undefined>): T { + if (value === undefinedValue || value === undefined || value === null) { + return fallbackValue; + } + + if (typeof value === typeof fallbackValue) { + return value; + } + + const mapper = mappers?.[typeof value]; + if (mapper) { + const mappedValue = mapper(value); + if (typeof mappedValue === typeof fallbackValue) { + return mappedValue; + } + } + return fallbackValue; +} diff --git a/test/input-utils.test.ts b/test/input-utils.test.ts index e766968..ccd5dc9 100644 --- a/test/input-utils.test.ts +++ b/test/input-utils.test.ts @@ -1,27 +1,29 @@ import { describe, test, expect, beforeAll, afterAll } from "@jest/globals"; import { setupInput, unsetInput } from "./utils/input-utils"; -import { getInputAsObject } from "../src/utils/input-utils"; +import { getInputAsObject, mapStringInput, mapObjectInput, mapNumberInput, mapBooleanInput } from "../src/utils/input-utils"; + +const defaultInput = { + "boolean": true, + "object": { foo: "bar" }, + "number": 1, + "array": ["foo", "bar"], + "undefined": "${undefined}", + + "files-primary": "primaryPath", + "files-secondary": "secondaryPath", + "files-secondary-inner": "innerSecondaryPath", + "files": "path", + + "modrinth-id": 42, + "modrinth-token": "1234", + "modrinth-files-primary": "primaryPath", + "modrinth-files-secondary": "secondaryPath", + + "This is a Very--Long_Name!": "foo" +}; describe("getInputAsObject", () => { - beforeAll(() => setupInput({ - "boolean": true, - "object": { foo: "bar" }, - "number": 1, - "array": ["foo", "bar"], - "undefined": "${undefined}", - - "files-primary": "primaryPath", - "files-secondary": "secondaryPath", - "files-secondary-inner": "innerSecondaryPath", - "files": "path", - - "modrinth-id": 42, - "modrinth-token": "1234", - "modrinth-files-primary": "primaryPath", - "modrinth-files-secondary": "secondaryPath", - - "This is a Very--Long_Name!": "foo" - })); + beforeAll(() => setupInput(defaultInput)); afterAll(() => unsetInput()); test("input object contains only strings", () => { @@ -71,3 +73,125 @@ describe("getInputAsObject", () => { expect(input.undefined).toBeUndefined(); }); }); + +describe("mapStringInput", () => { + beforeAll(() => setupInput(defaultInput)); + afterAll(() => unsetInput()); + + test("returns default value if input is not a string", () => { + const input = getInputAsObject(); + + expect(input["undefined"]).toBeUndefined(); + expect(mapStringInput(input["undefined"], "42")).toBe("42"); + }); + + test("maps strings to string", () => { + const input = getInputAsObject(); + expect(mapStringInput(input["boolean"], "")).toBe("true"); + expect(mapStringInput(input["number"], "")).toBe("1"); + expect(mapStringInput(input["object"])).toBe({}.toString()); + }); +}); + +describe("mapObjectInput", () => { + beforeAll(() => setupInput(defaultInput)); + afterAll(() => unsetInput()); + + test("returns default value if input is not an object", () => { + const input = getInputAsObject(); + + expect(input["boolean"]).not.toBeUndefined(); + expect(mapObjectInput(input["boolean"], null)).toBeNull(); + + expect(input["number"]).not.toBeUndefined(); + expect(mapObjectInput(input["number"], null)).toBeNull() + + expect(input["array"]).not.toBeUndefined(); + expect(mapObjectInput(input["array"])).toBeNull() + + expect(input["undefined"]).toBeUndefined(); + expect(mapObjectInput(input["undefined"], { answer: 42 })).toStrictEqual({ answer: 42 }); + }); + + test("maps object values to object", () => { + const input = getInputAsObject(); + expect(mapObjectInput(input["modrinth"], null)).toStrictEqual({ id: "42", token: "1234", filesPrimary: "primaryPath", filesSecondary: "secondaryPath", files: { primary: "primaryPath", secondary: "secondaryPath" } }); + }); +}); + +describe("mapNumberInput", () => { + beforeAll(() => setupInput({ + ...defaultInput, + numberOne: 1, + numberOneString: "1", + numberOneStringWithWhitespace: " 1 ", + })); + afterAll(() => unsetInput()); + + test("returns default value if input is not number or number-like", () => { + const input = getInputAsObject(); + + expect(input["boolean"]).not.toBeUndefined(); + expect(mapNumberInput(input["boolean"], 0)).toBe(0); + + expect(input["object"]).not.toBeUndefined(); + expect(mapNumberInput(input["object"], 0)).toBe(0); + + expect(input["array"]).not.toBeUndefined(); + expect(mapNumberInput(input["array"], 0)).toBe(0); + + expect(input["undefined"]).toBeUndefined(); + expect(mapNumberInput(input["undefined"], 1)).toBe(1); + }); + + test("maps number and number-like values to number", () => { + const input = getInputAsObject(); + + expect(mapNumberInput(input["numberone"], 0)).toBe(1); + expect(mapNumberInput(input["numberonestring"], 0)).toBe(1); + expect(mapNumberInput(input["numberonestringwithwhitespace"])).toBe(1); + }); +}); + +describe("mapBooleanInput", () => { + beforeAll(() => setupInput({ + ...defaultInput, + booleanTrue: true, + booleanTrueStringLowerCase: "true", + booleanTrueStringUpperCase: "TRUE", + booleanTrueStringUpperCaseWithWhitespace: " TRUE ", + booleanFalse: false, + booleanFalseStringLowerCase: "false", + booleanFalseStringUpperCase: "FALSE", + booleanFalseStringUpperCaseWithWhitespace: " FALSE ", + })); + afterAll(() => unsetInput()); + + test("returns default value if input is not boolean or boolean-like", () => { + const input = getInputAsObject(); + + expect(input["object"]).not.toBeUndefined(); + expect(mapBooleanInput(input["object"], false)).toBe(false); + + expect(input["number"]).not.toBeUndefined(); + expect(mapBooleanInput(input["number"], false)).toBe(false); + + expect(input["array"]).not.toBeUndefined(); + expect(mapBooleanInput(input["array"], false)).toBe(false); + + expect(input["undefined"]).toBeUndefined(); + expect(mapBooleanInput(input["undefined"], true)).toBe(true); + }); + + test("maps boolean and boolean-like values to boolean", () => { + const input = getInputAsObject(); + expect(mapBooleanInput(input["booleantrue"], false)).toBe(true); + expect(mapBooleanInput(input["booleantruestringlowercase"], false)).toBe(true); + expect(mapBooleanInput(input["booleantruestringuppercase"], false)).toBe(true); + expect(mapBooleanInput(input["booleantruestringuppercasewithwhitespace"])).toBe(true); + expect(mapBooleanInput(input["booleanfalse"], true)).toBe(false); + expect(mapBooleanInput(input["booleanfalsestringlowercase"], true)).toBe(false); + expect(mapBooleanInput(input["booleanfalsestringuppercase"], true)).toBe(false); + expect(mapBooleanInput(input["booleanfalsestringuppercasewithwhitespace"], true)).toBe(false); + }); +});