From cd728512897513db4da6ee5a502d7c1650ae893e Mon Sep 17 00:00:00 2001 From: Jesvin Jose Date: Tue, 7 Feb 2023 17:47:54 +0530 Subject: [PATCH] refactor: cli updates (#2907) Co-authored-by: Andrew Bastin --- packages/hoppscotch-cli/package.json | 2 +- .../src/__tests__/commands/test.spec.ts | 54 +++++++------ .../functions/checks/checkFile.spec.ts | 28 ------- .../collection/collectionsRunner.spec.ts | 8 +- .../mutators/parseCollectionData.spec.ts | 20 +++-- packages/hoppscotch-cli/src/commands/test.ts | 29 +++---- packages/hoppscotch-cli/src/handlers/error.ts | 7 +- .../hoppscotch-cli/src/options/test/delay.ts | 32 ++++---- .../hoppscotch-cli/src/options/test/env.ts | 73 +++++------------- packages/hoppscotch-cli/src/types/commands.ts | 6 +- packages/hoppscotch-cli/src/utils/checks.ts | 64 +--------------- .../hoppscotch-cli/src/utils/collections.ts | 9 +-- packages/hoppscotch-cli/src/utils/mutators.ts | 76 ++++++++----------- 13 files changed, 137 insertions(+), 271 deletions(-) delete mode 100644 packages/hoppscotch-cli/src/__tests__/functions/checks/checkFile.spec.ts diff --git a/packages/hoppscotch-cli/package.json b/packages/hoppscotch-cli/package.json index c86ec635b..ed292d111 100644 --- a/packages/hoppscotch-cli/package.json +++ b/packages/hoppscotch-cli/package.json @@ -1,6 +1,6 @@ { "name": "@hoppscotch/cli", - "version": "0.3.0", + "version": "0.3.1", "description": "A CLI to run Hoppscotch test scripts in CI environments.", "homepage": "https://hoppscotch.io", "main": "dist/index.js", diff --git a/packages/hoppscotch-cli/src/__tests__/commands/test.spec.ts b/packages/hoppscotch-cli/src/__tests__/commands/test.spec.ts index a1e6861a5..d471caa79 100644 --- a/packages/hoppscotch-cli/src/__tests__/commands/test.spec.ts +++ b/packages/hoppscotch-cli/src/__tests__/commands/test.spec.ts @@ -5,42 +5,52 @@ import { execAsync, getErrorCode, getTestJsonFilePath } from "../utils"; describe("Test 'hopp test ' command:", () => { test("No collection file path provided.", async () => { const cmd = `node ./bin/hopp test`; - const { stdout } = await execAsync(cmd); - const out = getErrorCode(stdout); + const { stderr } = await execAsync(cmd); + const out = getErrorCode(stderr); expect(out).toBe("INVALID_ARGUMENT"); }); test("Collection file not found.", async () => { const cmd = `node ./bin/hopp test notfound.json`; - const { stdout } = await execAsync(cmd); - const out = getErrorCode(stdout); + const { stderr } = await execAsync(cmd); + const out = getErrorCode(stderr); expect(out).toBe("FILE_NOT_FOUND"); }); - test("Malformed collection file.", async () => { + test("Collection file is invalid JSON.", async () => { const cmd = `node ./bin/hopp test ${getTestJsonFilePath( "malformed-collection.json" )}`; - const { stdout } = await execAsync(cmd); - const out = getErrorCode(stdout); + const { stderr } = await execAsync(cmd); + const out = getErrorCode(stderr); + + expect(out).toBe("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("MALFORMED_COLLECTION"); }); test("Invalid arguement.", async () => { const cmd = `node ./bin/hopp invalid-arg`; - const { stdout } = await execAsync(cmd); - const out = getErrorCode(stdout); + const { stderr } = await execAsync(cmd); + const out = getErrorCode(stderr); expect(out).toBe("INVALID_ARGUMENT"); }); test("Collection file not JSON type.", async () => { const cmd = `node ./bin/hopp test ${getTestJsonFilePath("notjson.txt")}`; - const { stdout } = await execAsync(cmd); - const out = getErrorCode(stdout); + const { stderr } = await execAsync(cmd); + const out = getErrorCode(stderr); expect(out).toBe("INVALID_FILE_TYPE"); }); @@ -70,24 +80,24 @@ describe("Test 'hopp test --env ' command:", () => { test("No env file path provided.", async () => { const cmd = `${VALID_TEST_CMD} --env`; - const { stdout } = await execAsync(cmd); - const out = getErrorCode(stdout); + const { stderr } = await execAsync(cmd); + const out = getErrorCode(stderr); expect(out).toBe("INVALID_ARGUMENT"); }); test("ENV file not JSON type.", async () => { const cmd = `${VALID_TEST_CMD} --env ${getTestJsonFilePath("notjson.txt")}`; - const { stdout } = await execAsync(cmd); - const out = getErrorCode(stdout); + const { stderr } = await execAsync(cmd); + const out = getErrorCode(stderr); expect(out).toBe("INVALID_FILE_TYPE"); }); test("ENV file not found.", async () => { const cmd = `${VALID_TEST_CMD} --env notfound.json`; - const { stdout } = await execAsync(cmd); - const out = getErrorCode(stdout); + const { stderr } = await execAsync(cmd); + const out = getErrorCode(stderr); expect(out).toBe("FILE_NOT_FOUND"); }); @@ -109,17 +119,17 @@ describe("Test 'hopp test --delay ' command:", () => { test("No value passed to delay flag.", async () => { const cmd = `${VALID_TEST_CMD} --delay`; - const { stdout } = await execAsync(cmd); - const out = getErrorCode(stdout); + const { stderr } = await execAsync(cmd); + const out = getErrorCode(stderr); expect(out).toBe("INVALID_ARGUMENT"); }); test("Invalid value passed to delay flag.", async () => { const cmd = `${VALID_TEST_CMD} --delay 'NaN'`; - const { stdout } = await execAsync(cmd); - const out = getErrorCode(stdout); - + const { stderr } = await execAsync(cmd); + const out = getErrorCode(stderr); + console.log("invalid value thing", out) expect(out).toBe("INVALID_ARGUMENT"); }); diff --git a/packages/hoppscotch-cli/src/__tests__/functions/checks/checkFile.spec.ts b/packages/hoppscotch-cli/src/__tests__/functions/checks/checkFile.spec.ts deleted file mode 100644 index 2ae9ba885..000000000 --- a/packages/hoppscotch-cli/src/__tests__/functions/checks/checkFile.spec.ts +++ /dev/null @@ -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({ - code: "FILE_NOT_FOUND", - }); - }); - - test("File not of JSON type.", () => { - return expect( - checkFile("./src/__tests__/samples/notjson.txt")() - ).resolves.toSubsetEqualLeft({ - code: "INVALID_FILE_TYPE", - }); - }); - - test("Existing JSON file.", () => { - return expect( - checkFile("./src/__tests__/samples/passes.json")() - ).resolves.toBeRight(); - }); -}); diff --git a/packages/hoppscotch-cli/src/__tests__/functions/collection/collectionsRunner.spec.ts b/packages/hoppscotch-cli/src/__tests__/functions/collection/collectionsRunner.spec.ts index 04f869989..4bdf11439 100644 --- a/packages/hoppscotch-cli/src/__tests__/functions/collection/collectionsRunner.spec.ts +++ b/packages/hoppscotch-cli/src/__tests__/functions/collection/collectionsRunner.spec.ts @@ -50,7 +50,7 @@ describe("collectionsRunner", () => { test("Empty HoppCollection.", () => { return expect( - collectionsRunner({ collections: [], envs: SAMPLE_ENVS })() + collectionsRunner({ collections: [], envs: SAMPLE_ENVS }) ).resolves.toStrictEqual([]); }); @@ -66,7 +66,7 @@ describe("collectionsRunner", () => { }, ], envs: SAMPLE_ENVS, - })() + }) ).resolves.toMatchObject([]); }); @@ -84,7 +84,7 @@ describe("collectionsRunner", () => { }, ], envs: SAMPLE_ENVS, - })() + }) ).resolves.toMatchObject([ { path: "collection/request", @@ -116,7 +116,7 @@ describe("collectionsRunner", () => { }, ], envs: SAMPLE_ENVS, - })() + }) ).resolves.toMatchObject([ { path: "collection/folder/request", diff --git a/packages/hoppscotch-cli/src/__tests__/functions/mutators/parseCollectionData.spec.ts b/packages/hoppscotch-cli/src/__tests__/functions/mutators/parseCollectionData.spec.ts index 236858376..6159f9239 100644 --- a/packages/hoppscotch-cli/src/__tests__/functions/mutators/parseCollectionData.spec.ts +++ b/packages/hoppscotch-cli/src/__tests__/functions/mutators/parseCollectionData.spec.ts @@ -1,22 +1,20 @@ import { HoppCLIError } from "../../../types/errors"; import { parseCollectionData } from "../../../utils/mutators"; -import "@relmify/jest-fp-ts"; - describe("parseCollectionData", () => { test("Reading non-existing file.", () => { return expect( - parseCollectionData("./src/__tests__/samples/notexist.json")() - ).resolves.toSubsetEqualLeft({ + parseCollectionData("./src/__tests__/samples/notexist.json") + ).rejects.toMatchObject({ code: "FILE_NOT_FOUND", }); }); test("Unparseable JSON contents.", () => { return expect( - parseCollectionData("./src/__tests__/samples/malformed-collection.json")() - ).resolves.toSubsetEqualLeft({ - code: "MALFORMED_COLLECTION", + parseCollectionData("./src/__tests__/samples/malformed-collection.json") + ).rejects.toMatchObject({ + code: "UNKNOWN_ERROR", }); }); @@ -24,15 +22,15 @@ describe("parseCollectionData", () => { return expect( parseCollectionData( "./src/__tests__/samples/malformed-collection2.json" - )() - ).resolves.toSubsetEqualLeft({ + ) + ).rejects.toMatchObject({ code: "MALFORMED_COLLECTION", }); }); test("Valid HoppCollection.", () => { return expect( - parseCollectionData("./src/__tests__/samples/passes.json")() - ).resolves.toBeRight(); + parseCollectionData("./src/__tests__/samples/passes.json") + ).resolves.toBeTruthy(); }); }); diff --git a/packages/hoppscotch-cli/src/commands/test.ts b/packages/hoppscotch-cli/src/commands/test.ts index 2f8d6500c..bdc563892 100644 --- a/packages/hoppscotch-cli/src/commands/test.ts +++ b/packages/hoppscotch-cli/src/commands/test.ts @@ -1,5 +1,3 @@ -import * as TE from "fp-ts/TaskEither"; -import { pipe, flow } from "fp-ts/function"; import { collectionsRunner, collectionsRunnerExit, @@ -10,18 +8,23 @@ import { parseCollectionData } from "../utils/mutators"; import { parseEnvsData } from "../options/test/env"; import { TestCmdOptions } from "../types/commands"; import { parseDelayOption } from "../options/test/delay"; +import { HoppEnvs } from "../types/request"; +import { isHoppCLIError } from "../utils/checks"; export const test = (path: string, options: TestCmdOptions) => async () => { - await pipe( - TE.Do, - TE.bind("envs", () => parseEnvsData(options.env)), - TE.bind("collections", () => parseCollectionData(path)), - TE.bind("delay", () => parseDelayOption(options.delay)), - TE.chainTaskK(collectionsRunner), - TE.chainW(flow(collectionsRunnerResult, collectionsRunnerExit, TE.of)), - TE.mapLeft((e) => { - handleError(e); + try { + const delay = options.delay ? parseDelayOption(options.delay) : 0 + const envs = options.env ? await parseEnvsData(options.env) : { global: [], selected: [] } + const collections = await parseCollectionData(path) + + const report = await collectionsRunner({collections, envs, delay}) + const hasSucceeded = collectionsRunnerResult(report) + collectionsRunnerExit(hasSucceeded) + } catch(e) { + if(isHoppCLIError(e)) { + handleError(e) process.exit(1); - }) - )(); + } + else throw e + } }; diff --git a/packages/hoppscotch-cli/src/handlers/error.ts b/packages/hoppscotch-cli/src/handlers/error.ts index 7e640e2fa..e89b6dfbc 100644 --- a/packages/hoppscotch-cli/src/handlers/error.ts +++ b/packages/hoppscotch-cli/src/handlers/error.ts @@ -1,4 +1,3 @@ -import { log } from "console"; import * as S from "fp-ts/string"; import { HoppError, HoppErrorCode } from "../types/errors"; import { hasProperty, isSafeCommanderError } from "../utils/checks"; @@ -7,7 +6,7 @@ import { exceptionColors } from "../utils/getters"; 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. * @param e Error data to parse. * @returns Information in string format appropriately parsed, based on error type. @@ -81,6 +80,6 @@ export const handleError = (error: HoppError) => { } if (!S.isEmpty(ERROR_MSG)) { - log(ERROR_CODE, ERROR_MSG); + console.error(ERROR_CODE, ERROR_MSG); } -}; +}; \ No newline at end of file diff --git a/packages/hoppscotch-cli/src/options/test/delay.ts b/packages/hoppscotch-cli/src/options/test/delay.ts index 939d0857d..75be423ed 100644 --- a/packages/hoppscotch-cli/src/options/test/delay.ts +++ b/packages/hoppscotch-cli/src/options/test/delay.ts @@ -1,20 +1,14 @@ -import * as TE from "fp-ts/TaskEither"; -import * as S from "fp-ts/string"; -import { pipe } from "fp-ts/function"; -import { error, HoppCLIError } from "../../types/errors"; +import { error } from "../../types/errors"; -export const parseDelayOption = ( - delay: unknown -): TE.TaskEither => - !S.isString(delay) - ? TE.right(0) - : pipe( - delay, - Number, - TE.fromPredicate(Number.isSafeInteger, () => - error({ - code: "INVALID_ARGUMENT", - data: "Expected '-d, --delay' value to be number", - }) - ) - ); +export function parseDelayOption(delay: string): number { + const maybeInt = Number.parseInt(delay) + + if(!Number.isNaN(maybeInt)) { + return maybeInt + } else { + throw error({ + code: "INVALID_ARGUMENT", + data: "Expected '-d, --delay' value to be number", + }) + } +} diff --git a/packages/hoppscotch-cli/src/options/test/env.ts b/packages/hoppscotch-cli/src/options/test/env.ts index 057bb8f0c..676a77664 100644 --- a/packages/hoppscotch-cli/src/options/test/env.ts +++ b/packages/hoppscotch-cli/src/options/test/env.ts @@ -1,64 +1,27 @@ -import fs from "fs/promises"; -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 { error } from "../../types/errors"; 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. * @param path Path of env.json file to be parsed. * @returns For successful parsing we get HoppEnvs object. */ -export const parseEnvsData = ( - path: unknown -): TE.TaskEither => - !S.isString(path) - ? TE.right({ global: [], selected: [] }) - : pipe( - // Checking if the env.json file exists or not. - checkFile(path), +export async function parseEnvsData(path: string) { + const contents = await readJsonFile(path) - // Trying to read given env json file path. - TE.chainW((checkedPath) => - TE.tryCatch( - () => fs.readFile(checkedPath), - (reason) => - error({ code: "UNKNOWN_ERROR", data: E.toError(reason) }) - ) - ), + if(!(contents && typeof contents === "object" && !Array.isArray(contents))) { + throw error({ code: "MALFORMED_ENV_FILE", path, data: null }) + } - // Trying to JSON parse the read file data and mapping the entries to HoppEnvPairs. - TE.chainEitherKW((data) => - pipe( - data.toString(), - J.parse, - E.map((jsonData) => - jsonData && typeof jsonData === "object" && !isArray(jsonData) - ? pipe( - jsonData, - Object.entries, - A.map( - ([key, value]) => - { - key, - value: S.isString(value) - ? value - : JSON.stringify(value), - } - ) - ) - : [] - ), - E.map((envPairs) => { global: [], selected: envPairs }), - E.mapLeft((e) => - error({ code: "MALFORMED_ENV_FILE", path, data: E.toError(e) }) - ) - ) - ) - ); + const envPairs: Array = [] + + for( const [key,value] of Object.entries(contents)) { + if(typeof value !== "string") { + throw error({ code: "MALFORMED_ENV_FILE", path, data: {value: value} }) + } + + envPairs.push({key, value}) + } + return { global: [], selected: envPairs } +} diff --git a/packages/hoppscotch-cli/src/types/commands.ts b/packages/hoppscotch-cli/src/types/commands.ts index 459576bf0..0c8eab855 100644 --- a/packages/hoppscotch-cli/src/types/commands.ts +++ b/packages/hoppscotch-cli/src/types/commands.ts @@ -1,6 +1,6 @@ export type TestCmdOptions = { - env: string; - delay: number; + env: string | undefined; + delay: string | undefined; }; -export type HoppEnvFileExt = "json"; +export type HOPP_ENV_FILE_EXT = "json"; diff --git a/packages/hoppscotch-cli/src/utils/checks.ts b/packages/hoppscotch-cli/src/utils/checks.ts index 53764ce15..dc184b528 100644 --- a/packages/hoppscotch-cli/src/utils/checks.ts +++ b/packages/hoppscotch-cli/src/utils/checks.ts @@ -1,20 +1,11 @@ -import fs from "fs/promises"; -import { join } from "path"; -import { pipe } from "fp-ts/function"; import { HoppCollection, HoppRESTRequest, isHoppRESTRequest, } from "@hoppscotch/data"; 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 { error, HoppCLIError, HoppErrnoException } from "../types/errors"; -import { HoppCollectionFileExt } from "../types/collections"; -import { HoppEnvFileExt } from "../types/commands"; +import { HoppCLIError, HoppErrnoException } from "../types/errors"; /** * Determines whether an object has a property with given name. @@ -71,59 +62,6 @@ export const isRESTCollection = ( 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 => - 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 => - 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 diff --git a/packages/hoppscotch-cli/src/utils/collections.ts b/packages/hoppscotch-cli/src/utils/collections.ts index 84e3806c5..c671d7c96 100644 --- a/packages/hoppscotch-cli/src/utils/collections.ts +++ b/packages/hoppscotch-cli/src/utils/collections.ts @@ -1,4 +1,3 @@ -import * as T from "fp-ts/Task"; import * as A from "fp-ts/Array"; import { pipe } from "fp-ts/function"; import { bold } from "chalk"; @@ -43,8 +42,8 @@ const { WARN, FAIL } = exceptionColors; * @returns List of report for each processed request. */ export const collectionsRunner = - (param: CollectionRunnerParam): T.Task => - async () => { + async (param: CollectionRunnerParam): Promise => + { const envs: HoppEnvs = param.envs; const delay = param.delay ?? 0; const requestsReport: RequestReport[] = []; @@ -213,10 +212,10 @@ export const collectionsRunnerResult = ( * Else, exit with code 1. * @param result Boolean defining the collections-runner result. */ -export const collectionsRunnerExit = (result: boolean) => { +export const collectionsRunnerExit = (result: boolean): never => { if (!result) { const EXIT_MSG = FAIL(`\nExited with code 1`); - process.stdout.write(EXIT_MSG); + process.stderr.write(EXIT_MSG); process.exit(1); } process.exit(0); diff --git a/packages/hoppscotch-cli/src/utils/mutators.ts b/packages/hoppscotch-cli/src/utils/mutators.ts index 990ce4595..1015ed2de 100644 --- a/packages/hoppscotch-cli/src/utils/mutators.ts +++ b/packages/hoppscotch-cli/src/utils/mutators.ts @@ -1,12 +1,7 @@ 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 { error, HoppCLIError } from "../types/errors"; -import { isRESTCollection, isHoppErrnoException, checkFile } from "./checks"; +import { error } from "../types/errors"; +import { isRESTCollection, isHoppErrnoException } from "./checks"; import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"; /** @@ -39,49 +34,44 @@ export const parseErrorMessage = (e: unknown) => { return msg.replace(/\n+$|\s{2,}/g, "").trim(); }; +export async function readJsonFile(path: string): Promise { + 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 * the parsed collectiona array. * @param path Collection json file path. * @returns For successful parsing we get array of HoppCollection, */ -export const parseCollectionData = ( +export async function parseCollectionData( path: string -): TE.TaskEither[]> => - pipe( - TE.of(path), +): Promise[]> { + let contents = await readJsonFile(path) - // Checking if given file path exists or not. - TE.chain(checkFile), + const maybeArrayOfCollections: unknown[] = Array.isArray(contents) ? contents : [contents] - // Trying to read give collection json path. - TE.chainW((checkedPath) => - TE.tryCatch( - () => fs.readFile(checkedPath), - (reason) => error({ code: "UNKNOWN_ERROR", data: E.toError(reason) }) - ) - ), + if(maybeArrayOfCollections.some((x) => !isRESTCollection(x))) { + throw error({ + code: "MALFORMED_COLLECTION", + path, + data: "Please check the collection data.", + }) + } - // Checking if parsed file data is array. - 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.", - }) - ) - ) - ); + return maybeArrayOfCollections as HoppCollection[] +};