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

Add typed errors do config module #85

Merged
merged 4 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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 .github/workflows/cli.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ jobs:
- name: Install dependencies
run: pnpm install

- name: Test
working-directory: ./apps/cli
run: pnpm test run

- name: Build
run: pnpm build --filter @cartesi/cli

Expand Down
96 changes: 81 additions & 15 deletions apps/cli/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,78 @@
import { extname } from "path";
import { TomlPrimitive, parse as parseToml } from "smol-toml";

/**
* Typed Errors
*/
export class InvalidBuilderError extends Error {
constructor(builder: any) {

Check warning on line 10 in apps/cli/src/config.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

Check warning on line 10 in apps/cli/src/config.ts

View workflow job for this annotation

GitHub Actions / build

Unexpected any. Specify a different type
endersonmaia marked this conversation as resolved.
Show resolved Hide resolved
super(`Invalid builder: ${builder}`);
this.name = "InvalidBuilder";
}
}

export class InvalidDriveFormatError extends Error {
constructor(format: any) {

Check warning on line 17 in apps/cli/src/config.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

Check warning on line 17 in apps/cli/src/config.ts

View workflow job for this annotation

GitHub Actions / build

Unexpected any. Specify a different type
endersonmaia marked this conversation as resolved.
Show resolved Hide resolved
super(`Invalid drive format: ${format}`);
this.name = "InvalidDriveFormatError";
}
}

export class InvalidEmptyDriveFormatError extends Error {
constructor(format: any) {

Check warning on line 24 in apps/cli/src/config.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

Check warning on line 24 in apps/cli/src/config.ts

View workflow job for this annotation

GitHub Actions / build

Unexpected any. Specify a different type
endersonmaia marked this conversation as resolved.
Show resolved Hide resolved
super(`Invalid empty drive format: ${format}`);
this.name = "InvalidEmptyDriveFormatError";
}
}

export class InvalidStringValueError extends Error {
constructor(value: any) {

Check warning on line 31 in apps/cli/src/config.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

Check warning on line 31 in apps/cli/src/config.ts

View workflow job for this annotation

GitHub Actions / build

Unexpected any. Specify a different type
super(`Invalid string value: ${value}`);
this.name = "InvalidStringValueError";
}
}

export class InvalidBooleanValueError extends Error {
constructor(value: any) {

Check warning on line 38 in apps/cli/src/config.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

Check warning on line 38 in apps/cli/src/config.ts

View workflow job for this annotation

GitHub Actions / build

Unexpected any. Specify a different type
super(`Invalid boolean value: ${value}`);
this.name = "InvalidBooleanValueError";
}
}

export class InvalidNumberValueError extends Error {
constructor(value: any) {

Check warning on line 45 in apps/cli/src/config.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

Check warning on line 45 in apps/cli/src/config.ts

View workflow job for this annotation

GitHub Actions / build

Unexpected any. Specify a different type
super(`Invalid number value: ${value}`);
this.name = "InvalidNumberValueError";
}
}

export class InvalidBytesValueError extends Error {
constructor(value: any) {

Check warning on line 52 in apps/cli/src/config.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

Check warning on line 52 in apps/cli/src/config.ts

View workflow job for this annotation

GitHub Actions / build

Unexpected any. Specify a different type
super(`Invalid bytes value: ${value}`);
this.name = "InvalidBytesValueError";
}
}

export class RequiredFieldError extends Error {
constructor(key: string) {
super(`Missing required field: ${key}`);
this.name = "RequiredFieldError";
}
}

export class InvalidStringArrayError extends Error {
constructor() {
super(`Invalid string array`);
this.name = "InvalidStringArrayError";
}
}

/**
* Configuration for drives of a Cartesi Machine. A drive may already exist or be built by a builder
*/
const DEFAULT_FORMAT = "ext2";
const DEFAULT_RAM = "128Mi";
const DEFAULT_RAM_IMAGE_DOCKER = "/usr/share/cartesi-machine/images/linux.bin";

Check warning on line 77 in apps/cli/src/config.ts

View workflow job for this annotation

GitHub Actions / lint

'DEFAULT_RAM_IMAGE_DOCKER' is assigned a value but never used

Check warning on line 77 in apps/cli/src/config.ts

View workflow job for this annotation

GitHub Actions / build

'DEFAULT_RAM_IMAGE_DOCKER' is assigned a value but never used
const DEFAULT_RAM_IMAGE_LINUX = "/usr/share/cartesi-machine/images/linux.bin";
const DEFAULT_RAM_IMAGE_MAC =
"/opt/homebrew/share/cartesi-machine/images/linux.bin";
Expand Down Expand Up @@ -141,7 +207,7 @@
} else if (typeof value === "boolean") {
return value;
}
throw new Error(`Invalid boolean value: ${value}`);
throw new InvalidBooleanValueError(value);
};

const parseOptionalBoolean = (value: TomlPrimitive): boolean | undefined => {
Expand All @@ -150,7 +216,7 @@
} else if (typeof value === "boolean") {
return value;
}
throw new Error(`Invalid boolean value: ${value}`);
throw new InvalidBooleanValueError(value);
};

const parseString = (value: TomlPrimitive, defaultValue: string): string => {
Expand All @@ -159,7 +225,7 @@
} else if (typeof value === "string") {
return value;
}
throw new Error(`Invalid string value: ${value}`);
throw new InvalidStringValueError(value);
};

const parseStringArray = (value: TomlPrimitive): string[] => {
Expand All @@ -172,19 +238,19 @@
if (typeof v === "string") {
return v;
}
throw new Error(`Invalid string value: ${v}`);
throw new InvalidStringValueError(v);
});
}
throw new Error(`Invalid string array value: ${value}`);
throw new InvalidStringArrayError();
};

const parseRequiredString = (value: TomlPrimitive, key: string): string => {
if (value === undefined) {
throw new Error(`Missing required value: ${key}`);
throw new RequiredFieldError(key);
} else if (typeof value === "string") {
return value;
}
throw new Error(`Invalid string value: ${value}`);
throw new InvalidStringValueError(value);
};

const parseOptionalString = (value: TomlPrimitive): string | undefined => {
Expand All @@ -193,7 +259,7 @@
} else if (typeof value === "string") {
return value;
}
throw new Error(`Invalid string value: ${value}`);
throw new InvalidStringValueError(value);
};

const parseOptionalStringBoolean = (
Expand All @@ -206,7 +272,7 @@
} else if (typeof value === "boolean") {
return value;
}
throw new Error(`Invalid string value: ${value}`);
throw new InvalidStringValueError(value);
};

const parseOptionalNumber = (value: TomlPrimitive): bigint | undefined => {
Expand All @@ -217,7 +283,7 @@
} else if (typeof value === "number") {
return BigInt(value);
}
throw new Error(`Invalid number value: ${value}`);
throw new InvalidNumberValueError(value);
};

const parseBytes = (value: TomlPrimitive, defaultValue: number): number => {
Expand All @@ -228,7 +294,7 @@
} else if (typeof value === "number" || typeof value === "string") {
return bytes.parse(value);
}
throw new Error(`Invalid bytes value: ${value}`);
throw new InvalidBytesValueError(value);
};

const parseBuilder = (value: TomlPrimitive): Builder => {
Expand All @@ -248,7 +314,7 @@
return "tar";
}
}
throw new Error(`Invalid builder: ${value}`);
throw new InvalidBuilderError(value as string);
};

const parseFormat = (value: TomlPrimitive): DriveFormat => {
Expand All @@ -262,7 +328,7 @@
return "sqfs";
}
}
throw new Error(`Invalid format: ${value}`);
throw new InvalidDriveFormatError(value as string);
};

const parseEmptyFormat = (value: TomlPrimitive): "ext2" | "raw" => {
Expand All @@ -276,7 +342,7 @@
return "raw";
}
}
throw new Error(`Invalid format: ${value}`);
throw new InvalidEmptyDriveFormatError(value as string);
};

const parseMachine = (value: TomlPrimitive): MachineConfig => {
Expand Down Expand Up @@ -313,7 +379,7 @@
case ".sqfs":
return "sqfs";
default:
throw new Error(`Invalid drive format: ${extension}`);
throw new InvalidDriveFormatError(extension);
}
};

Expand Down
91 changes: 80 additions & 11 deletions apps/cli/test/config.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import { describe, expect, it } from "vitest";
import { defaultConfig, defaultMachineConfig, parse } from "../src/config.js";
import {
defaultConfig,
defaultMachineConfig,
InvalidBooleanValueError,
InvalidBuilderError,
InvalidDriveFormatError,
InvalidEmptyDriveFormatError,
InvalidStringValueError,
parse,
RequiredFieldError,
} from "../src/config.js";

describe("config", () => {
it("default config", () => {
Expand Down Expand Up @@ -41,47 +51,106 @@ shared = true`);

it("invalid drive: invalid builder", () => {
expect(() => parse('[drives.root]\nbuilder = "invalid"')).toThrowError(
"Invalid builder: invalid",
new InvalidBuilderError("invalid"),
);
expect(() => parse("[drives.root]\nbuilder = true")).toThrowError(
"Invalid builder: true",
new InvalidBuilderError(true),
);
expect(() => parse("[drives.root]\nbuilder = 10")).toThrowError(
"Invalid builder: 10",
new InvalidBuilderError(10),
);
expect(() => parse("[drives.root]\nbuilder = {}")).toThrowError(
"Invalid builder: [object Object]",
new InvalidBuilderError({}),
);
});

it("invalid drive: invalid format", () => {
expect(() => parse('[drives.root]\nformat = "invalid"')).toThrowError(
"Invalid format: invalid",
new InvalidDriveFormatError("invalid"),
);
expect(() => parse("[drives.root]\nformat = true")).toThrowError(
"Invalid format: true",
new InvalidDriveFormatError(true),
);
expect(() => parse("[drives.root]\nformat = 10")).toThrowError(
"Invalid format: 10",
new InvalidDriveFormatError(10),
);
expect(() => parse("[drives.root]\nformat = {}")).toThrowError(
"Invalid format: [object Object]",
new InvalidDriveFormatError({}),
);
});

it("invalid drive: invalid extension", () => {
const builderNone = `
[drives.none]
builder = "none"
filename = "./games/doom.xyzfs"
mount = "/usr/local/games/doom"
`;
expect(() => parse(builderNone)).toThrowError(
new InvalidDriveFormatError(".xyzfs"),
);
});

it("invalid drive: invalid mount", () => {
expect(() => parse("[drives.data]\nmount = 42")).toThrowError(
"Invalid string value: 42",
new InvalidStringValueError(42),
);
});

it("invalid empty drive: invalid fomat", () => {
expect(() =>
parse("[drives.data]\nbuilder = 'empty'\nformat = 42"),
).toThrowError(new InvalidEmptyDriveFormatError(42));
});

it("invalid boolean value", () => {
expect(() => parse("[machine]\nno-rollup = 42")).toThrowError(
new InvalidBooleanValueError(42),
);
});

it("invalid string value", () => {
const invalidTarDrive = `
[drives.data]
builder = "tar"
filename = 42 # invalid
format = "ext2"
`;
expect(() => parse(invalidTarDrive)).toThrowError(
new InvalidStringValueError(42),
);
});

it("required field", () => {
const invalidDirectoryDrive = `
[drives.data]
builder = "directory"
# directory = '' # required
`;
expect(() => parse(invalidDirectoryDrive)).toThrowError(
new RequiredFieldError("directory"), //XXX: how to know which field was required
);
});

it("machine-config", () => {
expect(parse("[machine]\nno-rollup = true")).toEqual({
const config = `
[machine]
no-rollup = true
`;
expect(parse(config)).toEqual({
...defaultConfig(),
machine: {
...defaultMachineConfig(),
noRollup: true,
},
});

const invalidConfig = `
${config}
bootargs = ["no4lvl", "quiet", false]
`;
expect(() => parse(invalidConfig)).toThrowError(
new InvalidStringValueError(false),
);
});
});
Loading