Skip to content

Commit 7e250f3

Browse files
committed
Render content validation errors in Homebrew Inspector
1 parent 57570bf commit 7e250f3

File tree

4 files changed

+69
-50
lines changed

4 files changed

+69
-50
lines changed

packages/datasworn-compiler-cli/src/commands/build.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,7 @@ export const command = buildCommand({
100100
case "ContentValidationFailedProblem":
101101
for (const err of problem.errors) {
102102
logger.error(
103-
`${fileName}: Content validation error at ${err.path.join(
104-
".",
105-
)}: ${err.message}`,
103+
`${fileName}: Content validation error at ${err.instancePath}: ${err.message}`,
106104
);
107105
totalErrors++;
108106
}
Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
export class ValidationError extends Error {
1+
export class ValidationError {
2+
public readonly instancePath: string;
3+
24
constructor(
3-
message: string,
4-
public readonly path: string[],
5-
opts?: ErrorOptions,
5+
public readonly message: string,
6+
path: string[],
67
) {
7-
super(message, opts);
8+
this.instancePath = "/" + path.map(encodeURIComponent).join("/");
89
}
910
}

packages/datasworn-compiler/src/validators/oracle-rollable.test.ts

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ describe("validateOracleRollable", () => {
8181
expect(result.isErr).toBe(true);
8282
if (result.isErr) {
8383
expect(result.error).toHaveLength(1);
84-
expect(result.error[0].path).toEqual(["dice"]);
84+
expect(result.error[0].instancePath).toEqual("/dice");
8585
}
8686
});
8787

@@ -111,7 +111,7 @@ describe("validateOracleRollable", () => {
111111
const result = validateOracleRollable(data);
112112
expect(unwrapErr(result)).toContainEqual(
113113
expect.objectContaining({
114-
path: ["rows", "0", "roll"],
114+
instancePath: "/rows/0/roll",
115115
message: "Both min and max must be defined.",
116116
}),
117117
);
@@ -132,7 +132,7 @@ describe("validateOracleRollable", () => {
132132
const result = validateOracleRollable(data);
133133
expect(unwrapErr(result)).toContainEqual(
134134
expect.objectContaining({
135-
path: ["rows", "0", "roll"],
135+
instancePath: "/rows/0/roll",
136136
message: "Both min and max must be defined.",
137137
}),
138138
);
@@ -145,7 +145,7 @@ describe("validateOracleRollable", () => {
145145
const result = validateOracleRollable(data);
146146
expect(unwrapErr(result)).toContainEqual(
147147
expect.objectContaining({
148-
path: ["rows", "0", "roll"],
148+
instancePath: "/rows/0/roll",
149149
message: "Min (75) must be less than max (50).",
150150
}),
151151
);
@@ -159,7 +159,7 @@ describe("validateOracleRollable", () => {
159159
const result = validateOracleRollable(data);
160160
expect(unwrapErr(result)).toContainEqual(
161161
expect.objectContaining({
162-
path: ["rows", "0", "roll"],
162+
instancePath: "/rows/0/roll",
163163
message: "Roll range must be between 1 and 100.",
164164
}),
165165
);
@@ -189,7 +189,7 @@ describe("validateOracleRollable", () => {
189189
const result = validateOracleRollable(data);
190190
expect(unwrapErr(result)).toContainEqual(
191191
expect.objectContaining({
192-
path: ["rows", "0", "roll"],
192+
instancePath: "/rows/0/roll",
193193
message: "First row must start at 1, but starts at 2.",
194194
}),
195195
);
@@ -201,7 +201,7 @@ describe("validateOracleRollable", () => {
201201
const result = validateOracleRollable(data);
202202
expect(unwrapErr(result)).toContainEqual(
203203
expect.objectContaining({
204-
path: ["rows", "1", "roll"],
204+
instancePath: "/rows/1/roll",
205205
message: "Final row must end at 100, but ends at 60.",
206206
}),
207207
);
@@ -213,11 +213,13 @@ describe("validateOracleRollable", () => {
213213
const result = validateOracleRollable(data);
214214
expect(result.isErr).toBe(true);
215215
if (result.isErr) {
216-
expect(result.error).toHaveLength(1);
217-
expect(result.error[0].message).toBe(
218-
"Roll range (45-100) must not overlap with previous row's range (1-50).",
219-
);
220-
expect(result.error[0].path).toEqual(["rows", "1", "roll"]);
216+
expect(result.error).toEqual([
217+
{
218+
message:
219+
"Roll range (45-100) must not overlap with previous row's range (1-50).",
220+
instancePath: "/rows/1/roll",
221+
},
222+
]);
221223
}
222224
});
223225

packages/obsidian/src/datastore/view/content-view.ts

Lines changed: 48 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { SchemaValidationFailedProblem } from "@ironvault/datasworn-compiler";
21
import { atOrChildOfPath, relativeTo } from "@ironvault/utils/paths";
32
import { DataManager } from "datastore/loader/manager";
43
import { html, nothing, render, TemplateResult } from "lit-html";
@@ -117,35 +116,54 @@ export class ContentView extends FileView {
117116
${when(activeResult, (res) => {
118117
if (res.isErr) {
119118
const problem = res.error;
120-
// TODO: is this a bit of a hack? the error classes don't serialize
121-
// over structured clone, so we need to check for the tag
122-
if (SchemaValidationFailedProblem.is(problem)) {
123-
return html`<div class="error-message">
124-
<strong>Error loading file:</strong>
125-
${map(
126-
problem.errors.sort((a, b) =>
127-
a.instancePath.localeCompare(b.instancePath),
128-
),
129-
(e) => html`
130-
<dl>
131-
${map(
132-
Object.entries(e),
133-
([key, value]) =>
134-
html`<dt data-key="${key}">${key}</dt>
135-
${typeof value === "string"
136-
? // prettier-ignore
137-
html`<dd data-key=${key}">${join(value.split('/'), html`&ZeroWidthSpace;/&ZeroWidthSpace;`)}</dd>`
138-
: // prettier-ignore
139-
html`<dd data-key="${key}">${JSON.stringify(value, undefined, 2)}</dd>`}`,
140-
)}
141-
</dl>
142-
`,
143-
)}
144-
</div>`;
145-
} else {
146-
return html`<p class="error-message">
147-
<strong>Error loading file:</strong> ${res.error.message}
148-
</p>`;
119+
switch (problem._tag) {
120+
case "SchemaValidationFailedProblem":
121+
return html`<div class="error-message">
122+
<strong>Error loading file:</strong>
123+
${map(
124+
problem.errors.sort((a, b) =>
125+
a.instancePath.localeCompare(b.instancePath),
126+
),
127+
(e) => html`
128+
<dl>
129+
${map(
130+
Object.entries(e),
131+
([key, value]) =>
132+
html`<dt data-key="${key}">${key}</dt>
133+
${typeof value === "string"
134+
? // prettier-ignore
135+
html`<dd data-key=${key}">${join(value.split('/'), html`&ZeroWidthSpace;/&ZeroWidthSpace;`)}</dd>`
136+
: // prettier-ignore
137+
html`<dd data-key="${key}">${JSON.stringify(value, undefined, 2)}</dd>`}`,
138+
)}
139+
</dl>
140+
`,
141+
)}
142+
</div>`;
143+
case "ContentValidationFailedProblem":
144+
return html`<div class="error-message">
145+
<strong>Content validation errors:</strong>
146+
<dl>
147+
${map(
148+
problem.errors.sort((a, b) =>
149+
a.instancePath.localeCompare(b.instancePath),
150+
),
151+
(e) =>
152+
// prettier-ignore
153+
html`<dt data-key="${e.instancePath}">${e.instancePath}</dt>
154+
<dd data-key="${e.instancePath}">${e.message}</dd> `,
155+
)}
156+
</dl>
157+
</div>`;
158+
case "ErrorProblem":
159+
return html`<p class="error-message">
160+
<strong>Error loading file:</strong> ${problem.message}
161+
</p>`;
162+
case "WrongDataswornVersionProblem":
163+
return html`<p class="error-message">
164+
<strong>Unsupported Datasworn version:</strong>
165+
${problem.message}
166+
</p>`;
149167
}
150168
} else {
151169
return html`<p>Successfully parsed.</p>`;

0 commit comments

Comments
 (0)