fix: include cause of parsing errors in action output logs (#431)

This commit is contained in:
Eden Zimbelman
2025-05-06 10:15:05 -07:00
committed by GitHub
parent c7dafcf1f9
commit ab965e58bd
4 changed files with 78 additions and 9 deletions
+5 -4
View File
@@ -55,6 +55,7 @@ export default class Content {
* @returns {Content} - the parsed JSON payload to use in requests.
*/
getContentPayload(config) {
const errors = [];
if (!config.inputs.payload) {
throw new SlackError(
config.core,
@@ -71,8 +72,7 @@ export default class Content {
return /** @type {Content} */ (content);
} catch (error) {
if (error instanceof Error) {
config.core.debug("Failed to parse input payload as YAML");
config.core.debug(error.message);
errors.push(error);
}
}
try {
@@ -90,11 +90,12 @@ export default class Content {
}
return JSON.parse(trimmed);
} catch (/** @type {any} */ error) {
errors.unshift(error);
throw new SlackError(
config.core,
"Invalid input! Failed to parse contents of the provided payload",
{
cause: error,
cause: { values: errors },
},
);
}
@@ -140,7 +141,7 @@ export default class Content {
config.core,
"Invalid input! Failed to parse contents of the provided payload file",
{
cause: error,
cause: { values: [error] },
},
);
}
+6 -1
View File
@@ -1,12 +1,17 @@
import core from "@actions/core";
/**
* @typedef Cause
* @property {Error[]} [values] - Caught exceptions.
*/
/**
* SlackError is a custom error wrapper for known errors of Slack GitHub Action.
*/
export default class SlackError extends Error {
/**
* @typedef Options
* @property {Error} [cause] - thrown exception of this error.
* @property {Cause} [cause] - Reason for an error.
*/
/**
+11 -3
View File
@@ -6,13 +6,21 @@ import send from "./send.js";
* from the send.js file for testing purposes.
*/
try {
send(core);
await send(core);
} catch (error) {
if (error instanceof Error) {
core.error(error.message);
core.debug(`${error.name} cause: ${error.cause}`);
core.debug(`${error.name} stack: ${error.stack}`);
/** @type {import('./errors.js').Cause} */
const causes = /** @type {any} */ (error.cause);
if (causes?.values) {
for (const cause of causes.values) {
core.info(`${cause.stack}`);
}
} else {
core.info(`${error.stack}`);
}
} else {
core.error(`${error}`);
}
throw error;
}
+56 -1
View File
@@ -1,6 +1,7 @@
import path from "node:path";
import core from "@actions/core";
import { assert } from "chai";
import { YAMLException } from "js-yaml";
import Config from "../src/config.js";
import Content from "../src/content.js";
import SlackError from "../src/errors.js";
@@ -190,6 +191,11 @@ describe("content", () => {
err.message,
"Invalid input! Failed to parse contents of the provided payload",
);
assert.isDefined(err.cause?.values);
assert.equal(err.cause.values.length, 2);
const [jsonError, yamlError] = err.cause.values;
assert.isTrue(jsonError instanceof SyntaxError);
assert.isTrue(yamlError instanceof YAMLException);
} else {
assert.fail("Failed to throw a SlackError", err);
}
@@ -334,8 +340,10 @@ describe("content", () => {
err.message,
"Invalid input! Failed to parse contents of the provided payload file",
);
assert.isDefined(err.cause?.values);
assert.equal(err.cause.values.length, 1);
assert.include(
err.cause.message,
err.cause.values[0].message,
"Invalid input! Failed to parse file extension unknown.md",
);
} else {
@@ -343,5 +351,52 @@ describe("content", () => {
}
}
});
it("fails if invalid JSON exists in the input payload", async () => {
mocks.core.getInput.withArgs("payload-file-path").returns("example.json");
mocks.fs.readFileSync
.withArgs(path.resolve("example.json"), "utf-8")
.returns(`{
"message": "a truncated file without an end`);
try {
await send(mocks.core);
assert.fail("Failed to throw for invalid JSON");
} catch (err) {
if (err instanceof SlackError) {
assert.include(
err.message,
"Invalid input! Failed to parse contents of the provided payload file",
);
assert.isDefined(err.cause?.values);
assert.equal(err.cause.values.length, 1);
assert.isTrue(err.cause.values[0] instanceof SyntaxError);
} else {
assert.fail("Failed to throw a SlackError", err);
}
}
});
it("fails if invalid YAML exists in the input payload", async () => {
mocks.core.getInput.withArgs("payload-file-path").returns("example.yaml");
mocks.fs.readFileSync
.withArgs(path.resolve("example.yaml"), "utf-8")
.returns(`- "message": "assigned": "values"`);
try {
await send(mocks.core);
assert.fail("Failed to throw for invalid YAML");
} catch (err) {
if (err instanceof SlackError) {
assert.include(
err.message,
"Invalid input! Failed to parse contents of the provided payload file",
);
assert.isDefined(err.cause?.values);
assert.equal(err.cause.values.length, 1);
assert.isTrue(err.cause.values[0] instanceof YAMLException);
} else {
assert.fail("Failed to throw a SlackError", err);
}
}
});
});
});