refactor: cli updates (#2907)

Co-authored-by: Andrew Bastin <andrewbastin.k@gmail.com>
This commit is contained in:
Jesvin Jose
2023-02-07 17:47:54 +05:30
committed by GitHub
parent f676f94278
commit cd72851289
13 changed files with 137 additions and 271 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "@hoppscotch/cli", "name": "@hoppscotch/cli",
"version": "0.3.0", "version": "0.3.1",
"description": "A CLI to run Hoppscotch test scripts in CI environments.", "description": "A CLI to run Hoppscotch test scripts in CI environments.",
"homepage": "https://hoppscotch.io", "homepage": "https://hoppscotch.io",
"main": "dist/index.js", "main": "dist/index.js",

View File

@@ -5,42 +5,52 @@ import { execAsync, getErrorCode, getTestJsonFilePath } from "../utils";
describe("Test 'hopp test <file>' command:", () => { describe("Test 'hopp test <file>' command:", () => {
test("No collection file path provided.", async () => { test("No collection file path provided.", async () => {
const cmd = `node ./bin/hopp test`; const cmd = `node ./bin/hopp test`;
const { stdout } = await execAsync(cmd); const { stderr } = await execAsync(cmd);
const out = getErrorCode(stdout); const out = getErrorCode(stderr);
expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT"); expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
}); });
test("Collection file not found.", async () => { test("Collection file not found.", async () => {
const cmd = `node ./bin/hopp test notfound.json`; const cmd = `node ./bin/hopp test notfound.json`;
const { stdout } = await execAsync(cmd); const { stderr } = await execAsync(cmd);
const out = getErrorCode(stdout); const out = getErrorCode(stderr);
expect(out).toBe<HoppErrorCode>("FILE_NOT_FOUND"); expect(out).toBe<HoppErrorCode>("FILE_NOT_FOUND");
}); });
test("Malformed collection file.", async () => { test("Collection file is invalid JSON.", async () => {
const cmd = `node ./bin/hopp test ${getTestJsonFilePath( const cmd = `node ./bin/hopp test ${getTestJsonFilePath(
"malformed-collection.json" "malformed-collection.json"
)}`; )}`;
const { stdout } = await execAsync(cmd); const { stderr } = await execAsync(cmd);
const out = getErrorCode(stdout); const out = getErrorCode(stderr);
expect(out).toBe<HoppErrorCode>("UNKNOWN_ERROR");
});
test("Malformed collection file.", async () => {
const cmd = `node ./bin/hopp test ${getTestJsonFilePath(
"malformed-collection2.json"
)}`;
const { stderr } = await execAsync(cmd);
const out = getErrorCode(stderr);
expect(out).toBe<HoppErrorCode>("MALFORMED_COLLECTION"); expect(out).toBe<HoppErrorCode>("MALFORMED_COLLECTION");
}); });
test("Invalid arguement.", async () => { test("Invalid arguement.", async () => {
const cmd = `node ./bin/hopp invalid-arg`; const cmd = `node ./bin/hopp invalid-arg`;
const { stdout } = await execAsync(cmd); const { stderr } = await execAsync(cmd);
const out = getErrorCode(stdout); const out = getErrorCode(stderr);
expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT"); expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
}); });
test("Collection file not JSON type.", async () => { test("Collection file not JSON type.", async () => {
const cmd = `node ./bin/hopp test ${getTestJsonFilePath("notjson.txt")}`; const cmd = `node ./bin/hopp test ${getTestJsonFilePath("notjson.txt")}`;
const { stdout } = await execAsync(cmd); const { stderr } = await execAsync(cmd);
const out = getErrorCode(stdout); const out = getErrorCode(stderr);
expect(out).toBe<HoppErrorCode>("INVALID_FILE_TYPE"); expect(out).toBe<HoppErrorCode>("INVALID_FILE_TYPE");
}); });
@@ -70,24 +80,24 @@ describe("Test 'hopp test <file> --env <file>' command:", () => {
test("No env file path provided.", async () => { test("No env file path provided.", async () => {
const cmd = `${VALID_TEST_CMD} --env`; const cmd = `${VALID_TEST_CMD} --env`;
const { stdout } = await execAsync(cmd); const { stderr } = await execAsync(cmd);
const out = getErrorCode(stdout); const out = getErrorCode(stderr);
expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT"); expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
}); });
test("ENV file not JSON type.", async () => { test("ENV file not JSON type.", async () => {
const cmd = `${VALID_TEST_CMD} --env ${getTestJsonFilePath("notjson.txt")}`; const cmd = `${VALID_TEST_CMD} --env ${getTestJsonFilePath("notjson.txt")}`;
const { stdout } = await execAsync(cmd); const { stderr } = await execAsync(cmd);
const out = getErrorCode(stdout); const out = getErrorCode(stderr);
expect(out).toBe<HoppErrorCode>("INVALID_FILE_TYPE"); expect(out).toBe<HoppErrorCode>("INVALID_FILE_TYPE");
}); });
test("ENV file not found.", async () => { test("ENV file not found.", async () => {
const cmd = `${VALID_TEST_CMD} --env notfound.json`; const cmd = `${VALID_TEST_CMD} --env notfound.json`;
const { stdout } = await execAsync(cmd); const { stderr } = await execAsync(cmd);
const out = getErrorCode(stdout); const out = getErrorCode(stderr);
expect(out).toBe<HoppErrorCode>("FILE_NOT_FOUND"); expect(out).toBe<HoppErrorCode>("FILE_NOT_FOUND");
}); });
@@ -109,17 +119,17 @@ describe("Test 'hopp test <file> --delay <delay_in_ms>' command:", () => {
test("No value passed to delay flag.", async () => { test("No value passed to delay flag.", async () => {
const cmd = `${VALID_TEST_CMD} --delay`; const cmd = `${VALID_TEST_CMD} --delay`;
const { stdout } = await execAsync(cmd); const { stderr } = await execAsync(cmd);
const out = getErrorCode(stdout); const out = getErrorCode(stderr);
expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT"); expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
}); });
test("Invalid value passed to delay flag.", async () => { test("Invalid value passed to delay flag.", async () => {
const cmd = `${VALID_TEST_CMD} --delay 'NaN'`; const cmd = `${VALID_TEST_CMD} --delay 'NaN'`;
const { stdout } = await execAsync(cmd); const { stderr } = await execAsync(cmd);
const out = getErrorCode(stdout); const out = getErrorCode(stderr);
console.log("invalid value thing", out)
expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT"); expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
}); });

View File

@@ -1,28 +0,0 @@
import { HoppCLIError } from "../../../types/errors";
import { checkFile } from "../../../utils/checks";
import "@relmify/jest-fp-ts";
describe("checkFile", () => {
test("File doesn't exists.", () => {
return expect(
checkFile("./src/samples/this-file-not-exists.json")()
).resolves.toSubsetEqualLeft(<HoppCLIError>{
code: "FILE_NOT_FOUND",
});
});
test("File not of JSON type.", () => {
return expect(
checkFile("./src/__tests__/samples/notjson.txt")()
).resolves.toSubsetEqualLeft(<HoppCLIError>{
code: "INVALID_FILE_TYPE",
});
});
test("Existing JSON file.", () => {
return expect(
checkFile("./src/__tests__/samples/passes.json")()
).resolves.toBeRight();
});
});

View File

@@ -50,7 +50,7 @@ describe("collectionsRunner", () => {
test("Empty HoppCollection.", () => { test("Empty HoppCollection.", () => {
return expect( return expect(
collectionsRunner({ collections: [], envs: SAMPLE_ENVS })() collectionsRunner({ collections: [], envs: SAMPLE_ENVS })
).resolves.toStrictEqual([]); ).resolves.toStrictEqual([]);
}); });
@@ -66,7 +66,7 @@ describe("collectionsRunner", () => {
}, },
], ],
envs: SAMPLE_ENVS, envs: SAMPLE_ENVS,
})() })
).resolves.toMatchObject([]); ).resolves.toMatchObject([]);
}); });
@@ -84,7 +84,7 @@ describe("collectionsRunner", () => {
}, },
], ],
envs: SAMPLE_ENVS, envs: SAMPLE_ENVS,
})() })
).resolves.toMatchObject([ ).resolves.toMatchObject([
{ {
path: "collection/request", path: "collection/request",
@@ -116,7 +116,7 @@ describe("collectionsRunner", () => {
}, },
], ],
envs: SAMPLE_ENVS, envs: SAMPLE_ENVS,
})() })
).resolves.toMatchObject([ ).resolves.toMatchObject([
{ {
path: "collection/folder/request", path: "collection/folder/request",

View File

@@ -1,22 +1,20 @@
import { HoppCLIError } from "../../../types/errors"; import { HoppCLIError } from "../../../types/errors";
import { parseCollectionData } from "../../../utils/mutators"; import { parseCollectionData } from "../../../utils/mutators";
import "@relmify/jest-fp-ts";
describe("parseCollectionData", () => { describe("parseCollectionData", () => {
test("Reading non-existing file.", () => { test("Reading non-existing file.", () => {
return expect( return expect(
parseCollectionData("./src/__tests__/samples/notexist.json")() parseCollectionData("./src/__tests__/samples/notexist.json")
).resolves.toSubsetEqualLeft(<HoppCLIError>{ ).rejects.toMatchObject(<HoppCLIError>{
code: "FILE_NOT_FOUND", code: "FILE_NOT_FOUND",
}); });
}); });
test("Unparseable JSON contents.", () => { test("Unparseable JSON contents.", () => {
return expect( return expect(
parseCollectionData("./src/__tests__/samples/malformed-collection.json")() parseCollectionData("./src/__tests__/samples/malformed-collection.json")
).resolves.toSubsetEqualLeft(<HoppCLIError>{ ).rejects.toMatchObject(<HoppCLIError>{
code: "MALFORMED_COLLECTION", code: "UNKNOWN_ERROR",
}); });
}); });
@@ -24,15 +22,15 @@ describe("parseCollectionData", () => {
return expect( return expect(
parseCollectionData( parseCollectionData(
"./src/__tests__/samples/malformed-collection2.json" "./src/__tests__/samples/malformed-collection2.json"
)() )
).resolves.toSubsetEqualLeft(<HoppCLIError>{ ).rejects.toMatchObject(<HoppCLIError>{
code: "MALFORMED_COLLECTION", code: "MALFORMED_COLLECTION",
}); });
}); });
test("Valid HoppCollection.", () => { test("Valid HoppCollection.", () => {
return expect( return expect(
parseCollectionData("./src/__tests__/samples/passes.json")() parseCollectionData("./src/__tests__/samples/passes.json")
).resolves.toBeRight(); ).resolves.toBeTruthy();
}); });
}); });

View File

@@ -1,5 +1,3 @@
import * as TE from "fp-ts/TaskEither";
import { pipe, flow } from "fp-ts/function";
import { import {
collectionsRunner, collectionsRunner,
collectionsRunnerExit, collectionsRunnerExit,
@@ -10,18 +8,23 @@ import { parseCollectionData } from "../utils/mutators";
import { parseEnvsData } from "../options/test/env"; import { parseEnvsData } from "../options/test/env";
import { TestCmdOptions } from "../types/commands"; import { TestCmdOptions } from "../types/commands";
import { parseDelayOption } from "../options/test/delay"; import { parseDelayOption } from "../options/test/delay";
import { HoppEnvs } from "../types/request";
import { isHoppCLIError } from "../utils/checks";
export const test = (path: string, options: TestCmdOptions) => async () => { export const test = (path: string, options: TestCmdOptions) => async () => {
await pipe( try {
TE.Do, const delay = options.delay ? parseDelayOption(options.delay) : 0
TE.bind("envs", () => parseEnvsData(options.env)), const envs = options.env ? await parseEnvsData(options.env) : <HoppEnvs>{ global: [], selected: [] }
TE.bind("collections", () => parseCollectionData(path)), const collections = await parseCollectionData(path)
TE.bind("delay", () => parseDelayOption(options.delay)),
TE.chainTaskK(collectionsRunner), const report = await collectionsRunner({collections, envs, delay})
TE.chainW(flow(collectionsRunnerResult, collectionsRunnerExit, TE.of)), const hasSucceeded = collectionsRunnerResult(report)
TE.mapLeft((e) => { collectionsRunnerExit(hasSucceeded)
handleError(e); } catch(e) {
if(isHoppCLIError(e)) {
handleError(e)
process.exit(1); process.exit(1);
}) }
)(); else throw e
}
}; };

View File

@@ -1,4 +1,3 @@
import { log } from "console";
import * as S from "fp-ts/string"; import * as S from "fp-ts/string";
import { HoppError, HoppErrorCode } from "../types/errors"; import { HoppError, HoppErrorCode } from "../types/errors";
import { hasProperty, isSafeCommanderError } from "../utils/checks"; import { hasProperty, isSafeCommanderError } from "../utils/checks";
@@ -7,7 +6,7 @@ import { exceptionColors } from "../utils/getters";
const { BG_FAIL } = exceptionColors; const { BG_FAIL } = exceptionColors;
/** /**
* Parses unknown error data and narrows it to get information realted to * Parses unknown error data and narrows it to get information related to
* error in string format. * error in string format.
* @param e Error data to parse. * @param e Error data to parse.
* @returns Information in string format appropriately parsed, based on error type. * @returns Information in string format appropriately parsed, based on error type.
@@ -81,6 +80,6 @@ export const handleError = <T extends HoppErrorCode>(error: HoppError<T>) => {
} }
if (!S.isEmpty(ERROR_MSG)) { if (!S.isEmpty(ERROR_MSG)) {
log(ERROR_CODE, ERROR_MSG); console.error(ERROR_CODE, ERROR_MSG);
} }
}; };

View File

@@ -1,20 +1,14 @@
import * as TE from "fp-ts/TaskEither"; import { error } from "../../types/errors";
import * as S from "fp-ts/string";
import { pipe } from "fp-ts/function";
import { error, HoppCLIError } from "../../types/errors";
export const parseDelayOption = ( export function parseDelayOption(delay: string): number {
delay: unknown const maybeInt = Number.parseInt(delay)
): TE.TaskEither<HoppCLIError, number> =>
!S.isString(delay) if(!Number.isNaN(maybeInt)) {
? TE.right(0) return maybeInt
: pipe( } else {
delay, throw error({
Number, code: "INVALID_ARGUMENT",
TE.fromPredicate(Number.isSafeInteger, () => data: "Expected '-d, --delay' value to be number",
error({ })
code: "INVALID_ARGUMENT", }
data: "Expected '-d, --delay' value to be number", }
})
)
);

View File

@@ -1,64 +1,27 @@
import fs from "fs/promises"; import { error } from "../../types/errors";
import { pipe } from "fp-ts/function";
import * as TE from "fp-ts/TaskEither";
import * as E from "fp-ts/Either";
import * as J from "fp-ts/Json";
import * as A from "fp-ts/Array";
import * as S from "fp-ts/string";
import isArray from "lodash/isArray";
import { HoppCLIError, error } from "../../types/errors";
import { HoppEnvs, HoppEnvPair } from "../../types/request"; import { HoppEnvs, HoppEnvPair } from "../../types/request";
import { checkFile } from "../../utils/checks"; import { readJsonFile } from "../../utils/mutators";
/** /**
* Parses env json file for given path and validates the parsed env json object. * Parses env json file for given path and validates the parsed env json object.
* @param path Path of env.json file to be parsed. * @param path Path of env.json file to be parsed.
* @returns For successful parsing we get HoppEnvs object. * @returns For successful parsing we get HoppEnvs object.
*/ */
export const parseEnvsData = ( export async function parseEnvsData(path: string) {
path: unknown const contents = await readJsonFile(path)
): TE.TaskEither<HoppCLIError, HoppEnvs> =>
!S.isString(path)
? TE.right({ global: [], selected: [] })
: pipe(
// Checking if the env.json file exists or not.
checkFile(path),
// Trying to read given env json file path. if(!(contents && typeof contents === "object" && !Array.isArray(contents))) {
TE.chainW((checkedPath) => throw error({ code: "MALFORMED_ENV_FILE", path, data: null })
TE.tryCatch( }
() => fs.readFile(checkedPath),
(reason) =>
error({ code: "UNKNOWN_ERROR", data: E.toError(reason) })
)
),
// Trying to JSON parse the read file data and mapping the entries to HoppEnvPairs. const envPairs: Array<HoppEnvPair> = []
TE.chainEitherKW((data) =>
pipe( for( const [key,value] of Object.entries(contents)) {
data.toString(), if(typeof value !== "string") {
J.parse, throw error({ code: "MALFORMED_ENV_FILE", path, data: {value: value} })
E.map((jsonData) => }
jsonData && typeof jsonData === "object" && !isArray(jsonData)
? pipe( envPairs.push({key, value})
jsonData, }
Object.entries, return <HoppEnvs>{ global: [], selected: envPairs }
A.map( }
([key, value]) =>
<HoppEnvPair>{
key,
value: S.isString(value)
? value
: JSON.stringify(value),
}
)
)
: []
),
E.map((envPairs) => <HoppEnvs>{ global: [], selected: envPairs }),
E.mapLeft((e) =>
error({ code: "MALFORMED_ENV_FILE", path, data: E.toError(e) })
)
)
)
);

View File

@@ -1,6 +1,6 @@
export type TestCmdOptions = { export type TestCmdOptions = {
env: string; env: string | undefined;
delay: number; delay: string | undefined;
}; };
export type HoppEnvFileExt = "json"; export type HOPP_ENV_FILE_EXT = "json";

View File

@@ -1,20 +1,11 @@
import fs from "fs/promises";
import { join } from "path";
import { pipe } from "fp-ts/function";
import { import {
HoppCollection, HoppCollection,
HoppRESTRequest, HoppRESTRequest,
isHoppRESTRequest, isHoppRESTRequest,
} from "@hoppscotch/data"; } from "@hoppscotch/data";
import * as A from "fp-ts/Array"; import * as A from "fp-ts/Array";
import * as S from "fp-ts/string";
import * as TE from "fp-ts/TaskEither";
import * as E from "fp-ts/Either";
import curryRight from "lodash/curryRight";
import { CommanderError } from "commander"; import { CommanderError } from "commander";
import { error, HoppCLIError, HoppErrnoException } from "../types/errors"; import { HoppCLIError, HoppErrnoException } from "../types/errors";
import { HoppCollectionFileExt } from "../types/collections";
import { HoppEnvFileExt } from "../types/commands";
/** /**
* Determines whether an object has a property with given name. * Determines whether an object has a property with given name.
@@ -71,59 +62,6 @@ export const isRESTCollection = (
return false; return false;
}; };
/**
* Checks if the file path matches the requried file type with of required extension.
* @param path The input file path to check.
* @param extension The required extension for input file path.
* @returns Absolute path for valid file extension OR HoppCLIError in case of error.
*/
export const checkFileExt = curryRight(
(
path: unknown,
extension: HoppCollectionFileExt | HoppEnvFileExt
): E.Either<HoppCLIError, string> =>
pipe(
path,
E.fromPredicate(S.isString, (_) => error({ code: "NO_FILE_PATH" })),
E.chainW(
E.fromPredicate(S.endsWith(`.${extension}`), (_) =>
error({ code: "INVALID_FILE_TYPE", data: extension })
)
)
)
);
/**
* Checks if the given file path exists and is of given type.
* @param path The input file path to check.
* @returns Absolute path for valid file path OR HoppCLIError in case of error.
*/
export const checkFile = (path: unknown): TE.TaskEither<HoppCLIError, string> =>
pipe(
path,
// Checking if path is string.
TE.fromPredicate(S.isString, () => error({ code: "NO_FILE_PATH" })),
/**
* After checking file path, we map file path to absolute path and check
* if file is of given extension type.
*/
TE.map(join),
TE.chainEitherK(checkFileExt("json")),
/**
* Trying to access given file path.
* If successfully accessed, we return the path from predicate step.
* Else return HoppCLIError with code FILE_NOT_FOUND.
*/
TE.chainFirstW((checkedPath) =>
TE.tryCatchK(
() => fs.access(checkedPath),
() => error({ code: "FILE_NOT_FOUND", path: checkedPath })
)()
)
);
/** /**
* Checks if given error data is of type HoppCLIError, based on existence * Checks if given error data is of type HoppCLIError, based on existence

View File

@@ -1,4 +1,3 @@
import * as T from "fp-ts/Task";
import * as A from "fp-ts/Array"; import * as A from "fp-ts/Array";
import { pipe } from "fp-ts/function"; import { pipe } from "fp-ts/function";
import { bold } from "chalk"; import { bold } from "chalk";
@@ -43,8 +42,8 @@ const { WARN, FAIL } = exceptionColors;
* @returns List of report for each processed request. * @returns List of report for each processed request.
*/ */
export const collectionsRunner = export const collectionsRunner =
(param: CollectionRunnerParam): T.Task<RequestReport[]> => async (param: CollectionRunnerParam): Promise<RequestReport[]> =>
async () => { {
const envs: HoppEnvs = param.envs; const envs: HoppEnvs = param.envs;
const delay = param.delay ?? 0; const delay = param.delay ?? 0;
const requestsReport: RequestReport[] = []; const requestsReport: RequestReport[] = [];
@@ -213,10 +212,10 @@ export const collectionsRunnerResult = (
* Else, exit with code 1. * Else, exit with code 1.
* @param result Boolean defining the collections-runner result. * @param result Boolean defining the collections-runner result.
*/ */
export const collectionsRunnerExit = (result: boolean) => { export const collectionsRunnerExit = (result: boolean): never => {
if (!result) { if (!result) {
const EXIT_MSG = FAIL(`\nExited with code 1`); const EXIT_MSG = FAIL(`\nExited with code 1`);
process.stdout.write(EXIT_MSG); process.stderr.write(EXIT_MSG);
process.exit(1); process.exit(1);
} }
process.exit(0); process.exit(0);

View File

@@ -1,12 +1,7 @@
import fs from "fs/promises"; import fs from "fs/promises";
import * as E from "fp-ts/Either";
import * as TE from "fp-ts/TaskEither";
import * as A from "fp-ts/Array";
import * as J from "fp-ts/Json";
import { pipe } from "fp-ts/function";
import { FormDataEntry } from "../types/request"; import { FormDataEntry } from "../types/request";
import { error, HoppCLIError } from "../types/errors"; import { error } from "../types/errors";
import { isRESTCollection, isHoppErrnoException, checkFile } from "./checks"; import { isRESTCollection, isHoppErrnoException } from "./checks";
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"; import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data";
/** /**
@@ -39,49 +34,44 @@ export const parseErrorMessage = (e: unknown) => {
return msg.replace(/\n+$|\s{2,}/g, "").trim(); return msg.replace(/\n+$|\s{2,}/g, "").trim();
}; };
export async function readJsonFile(path: string): Promise<unknown> {
if(!path.endsWith('.json')) {
throw error({ code: "INVALID_FILE_TYPE", data: path })
}
try {
await fs.access(path)
} catch (e) {
throw error({ code: "FILE_NOT_FOUND", path: path })
}
try {
return JSON.parse((await fs.readFile(path)).toString())
} catch(e) {
throw error({ code: "UNKNOWN_ERROR", data: e })
}
}
/** /**
* Parses collection json file for given path:context.path, and validates * Parses collection json file for given path:context.path, and validates
* the parsed collectiona array. * the parsed collectiona array.
* @param path Collection json file path. * @param path Collection json file path.
* @returns For successful parsing we get array of HoppCollection<HoppRESTRequest>, * @returns For successful parsing we get array of HoppCollection<HoppRESTRequest>,
*/ */
export const parseCollectionData = ( export async function parseCollectionData(
path: string path: string
): TE.TaskEither<HoppCLIError, HoppCollection<HoppRESTRequest>[]> => ): Promise<HoppCollection<HoppRESTRequest>[]> {
pipe( let contents = await readJsonFile(path)
TE.of(path),
// Checking if given file path exists or not. const maybeArrayOfCollections: unknown[] = Array.isArray(contents) ? contents : [contents]
TE.chain(checkFile),
// Trying to read give collection json path. if(maybeArrayOfCollections.some((x) => !isRESTCollection(x))) {
TE.chainW((checkedPath) => throw error({
TE.tryCatch( code: "MALFORMED_COLLECTION",
() => fs.readFile(checkedPath), path,
(reason) => error({ code: "UNKNOWN_ERROR", data: E.toError(reason) }) data: "Please check the collection data.",
) })
), }
// Checking if parsed file data is array. return maybeArrayOfCollections as HoppCollection<HoppRESTRequest>[]
TE.chainEitherKW((data) => };
pipe(
data.toString(),
J.parse,
E.map((jsonData) => (Array.isArray(jsonData) ? jsonData : [jsonData])),
E.mapLeft((e) =>
error({ code: "MALFORMED_COLLECTION", path, data: E.toError(e) })
)
)
),
// Validating collections to be HoppRESTCollection.
TE.chainW(
TE.fromPredicate(A.every(isRESTCollection), () =>
error({
code: "MALFORMED_COLLECTION",
path,
data: "Please check the collection data.",
})
)
)
);