Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DSDK-183] [TS] [DSDK] Error/Type safety with purify-ts #12

Merged
merged 6 commits into from
Jan 29, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/core/jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ import type { JestConfigWithTsJest } from "@ledgerhq/jest-config-dsdk";
const config: JestConfigWithTsJest = {
preset: "@ledgerhq/jest-config-dsdk",
setupFiles: ["<rootDir>/jest.setup.ts"],
testPathIgnorePatterns: ["<rootDir>/lib/"],
valpinkman marked this conversation as resolved.
Show resolved Hide resolved
collectCoverageFrom: [
// TODO: remove internal when the rest of the files are setup
"src/internal/**/*.ts",
"!src/**/*.stub.ts",
],
moduleNameMapper: {
"^@internal/(.*)$": "<rootDir>/src/internal/$1",
},
};

export default config;
5 changes: 4 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@
"dev": "tsc --watch",
"lint": "eslint --cache --ext .ts \"src\"",
"lint:fix": "eslint --cache --fix --ext .ts \"src\"",
"test:unit": "jest --coverage src",
"test": "jest src",
"test:watch": "pnpm test -- --watch",
"test:coverage": "pnpm test -- --coverage",
"module:init": "zx scripts/add-module.mjs",
"run:index": "ts-node src/index.ts"
},
"dependencies": {
"inversify": "^6.0.2",
"inversify-logger-middleware": "^3.1.0",
"purify-ts": "^2.0.3",
"reflect-metadata": "^0.2.1"
},
"devDependencies": {
Expand Down
5 changes: 4 additions & 1 deletion packages/core/src/di.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ export const makeContainer = ({
}: Partial<MakeContainerProps> = {}) => {
const container = new Container();
container.applyMiddleware(logger);
container.load(configModuleFactory({ mock }));
container.load(
configModuleFactory({ mock })
// modules go here
);

return container;
};
5 changes: 2 additions & 3 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import "reflect-metadata";

import { types } from "@internal/config/di/configTypes";
import { GetSdkVersionUseCase } from "@internal/config/usecase/GetSdkVersionUseCase";
import { makeContainer } from "./di";
import { types } from "./internal/config/di/configTypes";
import { GetSdkVersionUseCase } from "./internal/config/usecase/GetSdkVersionUseCase";

export * from "./api";
export * from "./transport";
Expand Down
11 changes: 8 additions & 3 deletions packages/core/src/internal/config/data/ConfigDataSource.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { Config } from "../model/Config";
import { Either } from "purify-ts";
import {
LocalConfigFailure,
RemoteConfigFailure,
} from "@internal/config/di/configTypes";
import { Config } from "@internal/config/model/Config";

// Describe the different data sources interfaces our application could have

export interface LocalConfigDataSource {
getConfig(): Config;
getConfig(): Either<LocalConfigFailure, Config>;
}

export interface RemoteConfigDataSource {
getConfig(): Promise<Config>;
getConfig(): Promise<Either<RemoteConfigFailure, Config>>;
}
2 changes: 1 addition & 1 deletion packages/core/src/internal/config/data/Dto.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Types used by the data sources (here an example for the remote data source)
// They will usually be the response of an API call that will need to be parsed
// into an object used by the application. (in our example: DTOConfig => Config)
// into an object used by the application. (in our example: ConfigDto => Config)

export type ConfigDto = {
version: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { injectable } from "inversify";
import { Config } from "../model/Config";
import { Either } from "purify-ts";
import { Config } from "@internal/config/model/Config";
import { LocalConfigDataSource } from "./ConfigDataSource";

/**
Expand All @@ -10,10 +11,10 @@ import { LocalConfigDataSource } from "./ConfigDataSource";

@injectable()
export class StubLocalConfigDataSource implements LocalConfigDataSource {
getConfig(): Config {
return {
getConfig(): Either<never, Config> {
return Either.of({
name: "DeviceSDK",
version: "0.0.0-mock.1",
};
});
}
}
Original file line number Diff line number Diff line change
@@ -1,43 +1,64 @@
import fs from "fs";
import { Either, Left } from "purify-ts";
import { JSONParseError, ReadFileError } from "@internal/config/di/configTypes";
import { LocalConfigDataSource } from "./ConfigDataSource";
import { FileLocalConfigDataSource } from "./LocalConfigDataSource";
import { StubLocalConfigDataSource } from "./LocalConfigDataSource.stub";

const readFileSyncSpy = jest.spyOn(fs, "readFileSync");
const jsonParse = jest.spyOn(JSON, "parse");

let datasource: LocalConfigDataSource;
describe("LocalConfigDataSource", () => {
describe("FileLocalConfigDataSource", () => {
beforeEach(() => {
readFileSyncSpy.mockClear();
jsonParse.mockClear();
datasource = new FileLocalConfigDataSource();
});

afterAll(() => {
readFileSyncSpy.mockRestore();
jsonParse.mockRestore();
});

it("should return an Either<never, Config>", () => {
readFileSyncSpy.mockReturnValue(
JSON.stringify({ name: "DeviceSDK", version: "0.0.0-spied.1" })
);
});

it("should return the config", () => {
expect(datasource.getConfig()).toEqual({
jsonParse.mockReturnValue({
name: "DeviceSDK",
version: "0.0.0-spied.1",
});
});
});

describe("StubLocalConfigDataSource", () => {
beforeEach(() => {
datasource = new StubLocalConfigDataSource();
expect(datasource.getConfig()).toStrictEqual(
Either.of({
name: "DeviceSDK",
version: "0.0.0-spied.1",
})
);
});

it("should return the config", () => {
expect(datasource.getConfig()).toEqual({
name: "DeviceSDK",
version: "0.0.0-mock.1",
it("should return an Either<ReadFileError, never> if readFileSync throws", () => {
const err = new Error("readFileSync error");
readFileSyncSpy.mockImplementation(() => {
throw err;
});

expect(datasource.getConfig()).toEqual(Left(new ReadFileError(err)));
});
});

afterAll(() => {
readFileSyncSpy.mockClear();
it("should return an Either<JSONParseError, never> if JSON.parse throws", () => {
const err = new Error("JSON.parse error");
readFileSyncSpy.mockReturnValue(
JSON.stringify({ name: "DeviceSDK", version: "0.0.0-spied.1" })
);

jsonParse.mockImplementation(() => {
throw err;
});

expect(datasource.getConfig()).toEqual(Left(new JSONParseError(err)));
});
});
});
32 changes: 24 additions & 8 deletions packages/core/src/internal/config/data/LocalConfigDataSource.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import fs from "fs";
import { injectable } from "inversify";
import { Config } from "../model/Config";
import { LocalConfigDataSource } from "./ConfigDataSource";
import path from "path";
import { Either } from "purify-ts";
import {
ReadFileError,
JSONParseError,
LocalConfigFailure,
} from "@internal/config/di/configTypes";
import { Config } from "@internal/config/model/Config";
import { LocalConfigDataSource } from "./ConfigDataSource";

/**
*
Expand All @@ -12,11 +18,21 @@ import path from "path";
*/
@injectable()
export class FileLocalConfigDataSource implements LocalConfigDataSource {
getConfig(): Config {
const version = fs.readFileSync(
path.join(__dirname, "version.json"),
"utf-8"
);
return JSON.parse(version) as Config;
getConfig(): Either<LocalConfigFailure, Config> {
return Either.encase(() =>
fs.readFileSync(path.join(__dirname, "version.json"), "utf-8")
)
.mapLeft((error) => {
console.log("readFileSync error");
return new ReadFileError(error);
})
.chain((str) => {
return Either.encase(() => JSON.parse(str) as Config).mapLeft(
(error) => {
console.log("JSON.parse error");
return new JSONParseError(error);
}
);
});
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import { injectable } from "inversify";
import { Either } from "purify-ts";
import { Config } from "@internal/config/model/Config";
import { RemoteConfigDataSource } from "./ConfigDataSource";
import { Config } from "../model/Config";

/**
* class RemoteRestConfigDataSource
* This is a remote data source that reads the config from a remote API (example).
*/
@injectable()
export class StubRemoteConfigDataSource implements RemoteConfigDataSource {
async getConfig(): Promise<Config> {
async getConfig(): Promise<Either<never, Config>> {
return new Promise((res) =>
res({
name: "DeviceSDK",
version: "0.0.0-fake.2",
})
res(
Either.of({
name: "DeviceSDK",
version: "0.0.0-fake.2",
})
)
);
}
}
Loading
Loading