Skip to content

Commit 70bd7e3

Browse files
authored
feat: expose error type (#276)
1 parent f42ff51 commit 70bd7e3

6 files changed

Lines changed: 147 additions & 18 deletions

File tree

src/compile.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { EtaError } from "./err.ts";
1+
import { EtaParseError } from "./err.ts";
22

33
/* TYPES */
44
import type { Eta } from "./core.ts";
@@ -33,7 +33,7 @@ export function compile(this: Eta, str: string, options?: Partial<Options>): Tem
3333
) as TemplateFunction; // eslint-disable-line no-new-func
3434
} catch (e) {
3535
if (e instanceof SyntaxError) {
36-
throw new EtaError(
36+
throw new EtaParseError(
3737
"Bad template syntax\n\n" +
3838
e.message +
3939
"\n" +

src/err.ts

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,39 @@ export class EtaError extends Error {
55
}
66
}
77

8+
export class EtaParseError extends EtaError {
9+
constructor(message: string) {
10+
super(message);
11+
this.name = "EtaParser Error";
12+
}
13+
}
14+
15+
export class EtaRuntimeError extends EtaError {
16+
constructor(message: string) {
17+
super(message);
18+
this.name = "EtaRuntime Error";
19+
}
20+
}
21+
22+
export class EtaFileResolutionError extends EtaError {
23+
constructor(message: string) {
24+
super(message);
25+
this.name = "EtaFileResolution Error";
26+
}
27+
}
28+
29+
export class EtaNameResolutionError extends EtaError {
30+
constructor(message: string) {
31+
super(message);
32+
this.name = "EtaNameResolution Error";
33+
}
34+
}
35+
836
/**
937
* Throws an EtaError with a nicely formatted error and message showing where in the template the error occurred.
1038
*/
1139

12-
export function ParseErr(message: string, str: string, indx: number): void {
40+
export function ParseErr(message: string, str: string, indx: number): never {
1341
const whitespace = str.slice(0, indx).split(/\n/);
1442

1543
const lineNo = whitespace.length;
@@ -26,10 +54,10 @@ export function ParseErr(message: string, str: string, indx: number): void {
2654
" " +
2755
Array(colNo).join(" ") +
2856
"^";
29-
throw new EtaError(message);
57+
throw new EtaParseError(message);
3058
}
3159

32-
export function RuntimeErr(originalError: Error, str: string, lineNo: number, path: string): void {
60+
export function RuntimeErr(originalError: Error, str: string, lineNo: number, path: string): never {
3361
// code gratefully taken from https://github.com/mde/ejs and adapted
3462

3563
const lines = str.split("\n");
@@ -47,7 +75,7 @@ export function RuntimeErr(originalError: Error, str: string, lineNo: number, pa
4775

4876
const header = filename ? filename + ":" + lineNo + "\n" : "line " + lineNo + "\n";
4977

50-
const err = new EtaError(header + context + "\n\n" + originalError.message);
78+
const err = new EtaRuntimeError(header + context + "\n\n" + originalError.message);
5179

5280
err.name = originalError.name; // the original name (e.g. ReferenceError) may be useful
5381

src/file-handling.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { EtaError } from "./err.ts";
1+
import { EtaFileResolutionError } from "./err.ts";
22

33
import * as path from "node:path";
44

@@ -17,7 +17,7 @@ export function readFile(this: EtaCore, path: string): string {
1717
// eslint-disable-line @typescript-eslint/no-explicit-any
1818
} catch (err: any) {
1919
if (err?.code === "ENOENT") {
20-
throw new EtaError(`Could not find template: ${path}`);
20+
throw new EtaFileResolutionError(`Could not find template: ${path}`);
2121
} else {
2222
throw err;
2323
}
@@ -36,7 +36,7 @@ export function resolvePath(
3636
const views = this.config.views;
3737

3838
if (!views) {
39-
throw new EtaError("Views directory is not defined");
39+
throw new EtaFileResolutionError("Views directory is not defined");
4040
}
4141

4242
const baseFilePath = options && options.filepath;
@@ -80,7 +80,7 @@ export function resolvePath(
8080

8181
return resolvedFilePath;
8282
} else {
83-
throw new EtaError(`Template '${templatePath}' is not in the views directory`);
83+
throw new EtaFileResolutionError(`Template '${templatePath}' is not in the views directory`);
8484
}
8585
}
8686

src/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
import { Eta as EtaCore } from "./core.ts";
22
import { readFile, resolvePath } from "./file-handling.ts";
3+
export {
4+
EtaError,
5+
EtaParseError,
6+
EtaRuntimeError,
7+
EtaFileResolutionError,
8+
EtaNameResolutionError,
9+
} from "./err.ts";
310

411
export class Eta extends EtaCore {
512
readFile = readFile;

src/render.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { EtaError } from "./err.ts";
1+
import { EtaNameResolutionError } from "./err.ts";
22

33
/* TYPES */
44
import type { Options } from "./config.ts";
@@ -31,7 +31,7 @@ function handleCache(this: Eta, template: string, options: Partial<Options>): Te
3131
if (cachedTemplate) {
3232
return cachedTemplate;
3333
} else {
34-
throw new EtaError("Failed to get template '" + template + "'");
34+
throw new EtaNameResolutionError("Failed to get template '" + template + "'");
3535
}
3636
}
3737
}

test/err.spec.ts

Lines changed: 100 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,41 @@
11
/* global it, expect, describe */
22

33
import path from "path";
4-
import { Eta } from "../src/index";
4+
import {
5+
Eta,
6+
EtaError,
7+
EtaParseError,
8+
EtaRuntimeError,
9+
EtaFileResolutionError,
10+
EtaNameResolutionError,
11+
} from "../src/index";
512

613
describe("ParseErr", () => {
714
const eta = new Eta();
815

9-
it("error while parsing", () => {
16+
it("error while parsing - renderString", () => {
1017
try {
1118
eta.renderString("template <%", {});
1219
} catch (ex) {
13-
expect((ex as Error).name).toBe("Eta Error");
14-
expect((ex as Error).message).toBe(`unclosed tag at line 1 col 10:
20+
expect(ex).toBeInstanceOf(EtaError);
21+
expect(ex).toBeInstanceOf(EtaParseError);
22+
expect((ex as EtaParseError).name).toBe("EtaParser Error");
23+
expect((ex as EtaParseError).message).toBe(`unclosed tag at line 1 col 10:
24+
25+
template <%
26+
^`);
27+
expect(ex instanceof Error).toBe(true);
28+
}
29+
});
30+
31+
it("error while parsing - compile", () => {
32+
try {
33+
eta.compile("template <%");
34+
} catch (ex) {
35+
expect(ex).toBeInstanceOf(EtaError);
36+
expect(ex).toBeInstanceOf(EtaParseError);
37+
expect((ex as EtaParseError).name).toBe("EtaParser Error");
38+
expect((ex as EtaParseError).message).toBe(`unclosed tag at line 1 col 10:
1539
1640
template <%
1741
^`);
@@ -29,8 +53,10 @@ describe("RuntimeErr", () => {
2953
try {
3054
eta.render("./runtime-error", {});
3155
} catch (ex) {
32-
expect((ex as Error).name).toBe("ReferenceError");
33-
expect((ex as Error).message).toBe(`${errorFilepath}:2
56+
expect(ex).toBeInstanceOf(EtaError);
57+
expect(ex).toBeInstanceOf(EtaRuntimeError);
58+
expect((ex as EtaRuntimeError).name).toBe("ReferenceError");
59+
expect((ex as EtaRuntimeError).message).toBe(`${errorFilepath}:2
3460
1|
3561
>> 2| <%= undefinedVariable %>
3662
3| Lorem Ipsum
@@ -39,3 +65,71 @@ undefinedVariable is not defined`);
3965
}
4066
});
4167
});
68+
69+
describe("EtaFileResolutionError", () => {
70+
it("error throws correctly when template does not exist", () => {
71+
const eta = new Eta({ debug: true, views: path.join(__dirname, "templates") });
72+
const errorFilepath = path.join(__dirname, "templates/not-existing-template.eta");
73+
74+
try {
75+
eta.render("./not-existing-template", {});
76+
} catch (ex) {
77+
expect(ex).toBeInstanceOf(EtaError);
78+
expect(ex).toBeInstanceOf(EtaFileResolutionError);
79+
expect((ex as EtaFileResolutionError).name).toBe("EtaFileResolution Error");
80+
expect((ex as EtaFileResolutionError).message).toBe(
81+
`Could not find template: ${errorFilepath}`
82+
);
83+
}
84+
});
85+
86+
it("error throws correctly when views options is missing", async () => {
87+
const eta = new Eta({ debug: true });
88+
try {
89+
eta.render("Hi", {});
90+
} catch (ex) {
91+
expect(ex).toBeInstanceOf(EtaFileResolutionError);
92+
expect((ex as EtaFileResolutionError).name).toBe("EtaFileResolution Error");
93+
expect((ex as EtaFileResolutionError).message).toBe("Views directory is not defined");
94+
}
95+
96+
try {
97+
await eta.renderAsync("Hi", {});
98+
} catch (ex) {
99+
expect(ex).toBeInstanceOf(EtaFileResolutionError);
100+
expect((ex as EtaFileResolutionError).name).toBe("EtaFileResolution Error");
101+
expect((ex as EtaFileResolutionError).message).toBe("Views directory is not defined");
102+
}
103+
});
104+
105+
it("error throws correctly when template in not in th view directory", () => {
106+
const eta = new Eta({ debug: true, views: path.join(__dirname, "templates") });
107+
108+
const filePath = "../../../simple.eta";
109+
try {
110+
eta.render(filePath, {});
111+
} catch (ex) {
112+
expect(ex).toBeInstanceOf(EtaFileResolutionError);
113+
expect((ex as EtaFileResolutionError).name).toBe("EtaFileResolution Error");
114+
expect((ex as EtaFileResolutionError).message).toBe(
115+
`Template '${filePath}' is not in the views directory`
116+
);
117+
}
118+
});
119+
});
120+
121+
describe("EtaNameResolutionError", () => {
122+
const eta = new Eta({ debug: true, views: path.join(__dirname, "templates") });
123+
124+
it("error throws correctly", () => {
125+
const template = "@not-existing-tp";
126+
127+
try {
128+
eta.render(template, {});
129+
} catch (ex) {
130+
expect(ex).toBeInstanceOf(EtaNameResolutionError);
131+
expect((ex as EtaNameResolutionError).name).toBe("EtaNameResolution Error");
132+
expect((ex as EtaNameResolutionError).message).toBe(`Failed to get template '${template}'`);
133+
}
134+
});
135+
});

0 commit comments

Comments
 (0)