Skip to content

Commit

Permalink
fix: improve file-parse error logs (#345)
Browse files Browse the repository at this point in the history
Improve the typing and logs for file-parse errors.
  • Loading branch information
ComradeVanti committed May 28, 2024
1 parent 2cb1d74 commit c8bc860
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 71 deletions.
22 changes: 15 additions & 7 deletions src/cli/error-logging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,26 @@ import { NoWslError } from "../io/wsl";
import { ChildProcessError } from "../utils/process";
import { RequiredEnvMissingError } from "../io/upm-config-io";
import { FileMissingError, GenericIOError } from "../io/common-errors";
import { StringFormatError } from "../utils/string-parsing";

/**
* Logs a {@link ManifestLoadError} to the console.
*/
export function logManifestLoadError(log: Logger, error: ManifestLoadError) {
const reason =
error instanceof FileMissingError
? `it could not be found at "${error.path}"`
: error instanceof GenericIOError
? "a file-system interaction failed"
: error instanceof StringFormatError
? "the manifest file did not contain valid json"
: "the manifest file did not contain a valid project manifest";

const prefix = "manifest";
if (error instanceof FileMissingError)
log.error(prefix, `manifest at ${error.path} does not exist`);
else if (error instanceof FileParseError) {
log.error(prefix, `failed to parse manifest at ${error.path}`);
if (error.cause !== undefined) log.error(prefix, error.cause.message);
}
const errorMessage = `Could not load project manifest because ${reason}.`;
log.error(prefix, errorMessage);

// TODO: Print fix suggestions
}

/**
Expand Down Expand Up @@ -69,7 +77,7 @@ export function logEnvParseError(log: Logger, error: EnvParseError) {
: error instanceof ChildProcessError
? "a required child process failed"
: error instanceof FileParseError
? `the file at ${error.path} could not parsed into a ${error.targetDescription}`
? `the project version file (ProjectVersion.txt) has an invalid structure`
: error instanceof RequiredEnvMissingError
? `none of the following environment variables were set: ${error.keyNames.join(
", "
Expand Down
14 changes: 5 additions & 9 deletions src/common-errors.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { CustomError } from "ts-custom-error";
import { EditorVersion } from "./domain/editor-version";
import { StringFormatError } from "./utils/string-parsing";

/**
* Error for when the packument was not found.
Expand All @@ -27,21 +26,18 @@ export class PackageWithVersionError extends CustomError {
/**
* Error for when a file could not be parsed into a specific target type.
*/
export class FileParseError extends CustomError {
export class FileParseError<const TTarget extends string> extends CustomError {
constructor(
/**
* The path to the file that could not be parsed.
*/
readonly path: string,
public readonly filePath: string,
/**
* A description or name of the thing that the file was supposed to be
* parsed to.
* parsed to. This should be a constant string that can be used in ifs
* and switches.
*/
readonly targetDescription: string,
/**
* The error that caused this one.
*/
readonly cause?: StringFormatError
public readonly targetDescription: TTarget
) {
super("A file could not be parsed into a specific target type.");
}
Expand Down
49 changes: 31 additions & 18 deletions src/io/project-manifest-io.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
UnityProjectManifest,
} from "../domain/project-manifest";
import path from "path";
import { AsyncResult } from "ts-results-es";
import { AsyncResult, Err, Ok } from "ts-results-es";
import { ReadTextFile, WriteTextFile } from "./fs-result";
import { StringFormatError, tryParseJson } from "../utils/string-parsing";
import { FileParseError } from "../common-errors";
Expand Down Expand Up @@ -33,13 +33,29 @@ export function makeProjectManifestMissingError(
return new FileMissingError("ProjectManifest", filePath);
}

/**
* Error for when the project manifest could not be parsed.
*/
export type ProjectManifestParseError = FileParseError<"ProjectManifest">;

/**
* Makes a {@link ProjectManifestParseError} object.
* @param filePath The path of the file.
*/
export function makeProjectManifestParseError(
filePath: string
): ProjectManifestParseError {
return new FileParseError(filePath, "ProjectManifest");
}

/**
* Error which may occur when loading a project manifest.
*/
export type ManifestLoadError =
| ProjectManifestMissingError
| GenericIOError
| FileParseError;
| StringFormatError
| ProjectManifestParseError;

/**
* Function for loading the project manifest for a Unity project.
Expand All @@ -57,22 +73,19 @@ export function makeProjectManifestLoader(
): LoadProjectManifest {
return (projectPath) => {
const manifestPath = manifestPathFor(projectPath);
return (
readFile(manifestPath)
.mapErr((error) =>
error.code === "ENOENT"
? makeProjectManifestMissingError(manifestPath)
: new GenericIOError()
)
.andThen(tryParseJson)
// TODO: Actually validate the json structure
.map((json) => json as unknown as UnityProjectManifest)
.mapErr((error) =>
error instanceof StringFormatError
? new FileParseError(manifestPath, "Project-manifest", error)
: error
)
);
return readFile(manifestPath)
.mapErr((error) =>
error.code === "ENOENT"
? makeProjectManifestMissingError(manifestPath)
: new GenericIOError()
)
.andThen(tryParseJson)
.andThen((json) =>
typeof json === "object"
? // TODO: Actually validate the json structure
Ok(json as unknown as UnityProjectManifest)
: Err(makeProjectManifestParseError(manifestPath))
);
};
}

Expand Down
23 changes: 19 additions & 4 deletions src/io/project-version-io.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,29 @@ export function makeProjectVersionMissingError(
return new FileMissingError("ProjectVersion.txt", filePath);
}

/**
* Error for when the project version could not be parsed.
*/
export type ProjectVersionParseError = FileParseError<"ProjectVersion.txt">;

/**
* Makes a {@link ProjectVersionParseError} object.
* @param filePath The path of the file.
*/
export function makeProjectVersionParseError(
filePath: string
): ProjectVersionParseError {
return new FileParseError(filePath, "ProjectVersion.txt");
}

/**
* Error which may occur when loading a project-version.
*/
export type ProjectVersionLoadError =
| StringFormatError
| FileParseError
| ProjectVersionMissingError
| GenericIOError;
| GenericIOError
| StringFormatError
| ProjectVersionParseError;

/**
* Function for loading a projects editor version string.
Expand Down Expand Up @@ -67,7 +82,7 @@ export function makeProjectVersionLoader(
typeof content.m_EditorVersion === "string"
)
)
return Err(new FileParseError(filePath, "Project-version"));
return Err(makeProjectVersionParseError(filePath));

return Ok(content.m_EditorVersion);
});
Expand Down
11 changes: 2 additions & 9 deletions src/services/parse-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,15 +158,8 @@ export function makeParseEnvService(

// editor version
const projectVersionLoadResult = await loadProjectVersion(cwd).promise;
if (projectVersionLoadResult.isErr()) {
const error = projectVersionLoadResult.error;
if (error instanceof FileParseError)
log.error(
"ProjectVersion",
"ProjectVersion.txt could not be parsed for editor-version!"
);
return projectVersionLoadResult;
}
if (projectVersionLoadResult.isErr()) return projectVersionLoadResult;

const unparsedEditorVersion = projectVersionLoadResult.value;
const parsedEditorVersion = tryParseEditorVersion(unparsedEditorVersion);
const editorVersion =
Expand Down
4 changes: 2 additions & 2 deletions test/io/project-manifest-io.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
mapScopedRegistry,
} from "../../src/domain/project-manifest";
import path from "path";
import { FileParseError } from "../../src/common-errors";
import { ReadTextFile, WriteTextFile } from "../../src/io/fs-result";
import { Err, Ok } from "ts-results-es";
import { buildProjectManifest } from "../domain/data-project-manifest";
Expand All @@ -18,6 +17,7 @@ import { exampleRegistryUrl } from "../domain/data-registry";
import { mockService } from "../services/service.mock";
import { eaccesError, enoentError } from "./node-error.mock";
import { FileMissingError, GenericIOError } from "../../src/io/common-errors";
import { StringFormatError } from "../../src/utils/string-parsing";

const exampleProjectPath = "/some/path";
describe("project-manifest io", () => {
Expand Down Expand Up @@ -72,7 +72,7 @@ describe("project-manifest io", () => {
const result = await loadProjectManifest(exampleProjectPath).promise;

expect(result).toBeError((actual) =>
expect(actual).toBeInstanceOf(FileParseError)
expect(actual).toBeInstanceOf(StringFormatError)
);
});

Expand Down
22 changes: 0 additions & 22 deletions test/services/parse-env.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { NpmAuth } from "another-npm-registry-client";
import { Env, makeParseEnvService } from "../../src/services/parse-env";
import { Err, Ok } from "ts-results-es";
import { GetUpmConfigPath, LoadUpmConfig } from "../../src/io/upm-config-io";
import { FileParseError } from "../../src/common-errors";
import { makeEditorVersion } from "../../src/domain/editor-version";
import { NoWslError } from "../../src/io/wsl";
import { mockUpmConfig } from "../io/upm-config-io.mock";
Expand Down Expand Up @@ -522,26 +521,5 @@ describe("env", () => {

expect(result).toBeError((error) => expect(error).toEqual(expected));
});

it("should notify of parsing issue", async () => {
const { parseEnv, log, loadProjectVersion } = makeDependencies();
loadProjectVersion.mockReturnValue(
Err(
new FileParseError(
"/some/path/ProjectVersion.txt",
"ProjectVersion.txt"
)
).toAsyncResult()
);

await parseEnv({
_global: {},
});

expect(log.error).toHaveBeenCalledWith(
"ProjectVersion",
expect.stringContaining("could not be parsed")
);
});
});
});

0 comments on commit c8bc860

Please sign in to comment.