Skip to content

Commit d360a86

Browse files
authored
Enforce array schema (#151)
- Fixed a bug where a single object incorrectly passed validation against a schema requiring an array. Updated `JSONValidation` in `validation.ts` to treat root-level errors as document-wide issues. - Fixed a regression where invalid YAML produced validation errors after the root-level error change. Added a check in `doValidation` to skip validation for unparseable input. - Added a test in json-validation.spec.ts to verify that a schema expecting an array correctly rejects a single object, boolean, string and number.
1 parent 5586190 commit d360a86

File tree

4 files changed

+86
-8
lines changed

4 files changed

+86
-8
lines changed

.changeset/hot-maps-marry.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"codemirror-json-schema": patch
3+
---
4+
5+
Fixed validation bugs: single objects incorrectly passed array schemas, invalid YAML caused errors after root-level change(now skipped if unparseable), and added tests ensuring non-array values(object, boolean, string, number) are correctly rejected.

src/features/__tests__/__fixtures__/schemas.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,3 +245,10 @@ export const wrappedTestSchemaConditionalPropertiesOnSameObject = {
245245
},
246246
required: ["original"],
247247
} as JSONSchema7;
248+
249+
export const testSchemaArrayOfObjects = {
250+
type: "array",
251+
items: {
252+
type: "object",
253+
},
254+
} as JSONSchema7;

src/features/__tests__/json-validation.spec.ts

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,19 @@ import type { Diagnostic } from "@codemirror/lint";
44
import { describe, it, expect } from "vitest";
55
import { EditorView } from "@codemirror/view";
66

7-
import { testSchema, testSchema2 } from "./__fixtures__/schemas";
7+
import {
8+
testSchema,
9+
testSchema2,
10+
testSchemaArrayOfObjects,
11+
} from "./__fixtures__/schemas";
812
import { JSONMode } from "../../types";
913
import { getExtensions } from "./__helpers__/index";
1014
import { MODES } from "../../constants";
1115

1216
const getErrors = (
1317
jsonString: string,
1418
mode: JSONMode,
15-
schema?: JSONSchema7
19+
schema?: JSONSchema7,
1620
) => {
1721
const view = new EditorView({
1822
doc: jsonString,
@@ -30,13 +34,13 @@ const expectErrors = (
3034
jsonString: string,
3135
errors: [from: number | undefined, to: number | undefined, message: string][],
3236
mode: JSONMode,
33-
schema?: JSONSchema7
37+
schema?: JSONSchema7,
3438
) => {
3539
const filteredErrors = getErrors(jsonString, mode, schema).map(
36-
({ renderMessage, ...error }) => error
40+
({ renderMessage, ...error }) => error,
3741
);
3842
expect(filteredErrors).toEqual(
39-
errors.map(([from, to, message]) => ({ ...common, from, to, message }))
43+
errors.map(([from, to, message]) => ({ ...common, from, to, message })),
4044
);
4145
};
4246

@@ -145,6 +149,65 @@ describe("json-validation", () => {
145149
],
146150
schema: testSchema2,
147151
},
152+
{
153+
name: "reject a single object when schema expects an array",
154+
mode: MODES.JSON,
155+
doc: '{ "name": "John" }',
156+
errors: [
157+
{
158+
from: 0,
159+
to: 0,
160+
message: "Expected `array` but received `object`",
161+
},
162+
],
163+
schema: testSchemaArrayOfObjects,
164+
},
165+
{
166+
name: "reject a boolean when schema expects an array",
167+
mode: MODES.JSON,
168+
doc: "true",
169+
errors: [
170+
{
171+
from: 0,
172+
to: 0,
173+
message: "Expected `array` but received `boolean`",
174+
},
175+
],
176+
schema: testSchemaArrayOfObjects,
177+
},
178+
{
179+
name: "reject a string when schema expects an array",
180+
mode: MODES.JSON,
181+
doc: '"example"',
182+
errors: [
183+
{
184+
from: 0,
185+
to: 0,
186+
message: "Expected `array` but received `string`",
187+
},
188+
],
189+
schema: testSchemaArrayOfObjects,
190+
},
191+
{
192+
name: "reject a number when schema expects an array",
193+
mode: MODES.JSON,
194+
doc: "123",
195+
errors: [
196+
{
197+
from: 0,
198+
to: 0,
199+
message: "Expected `array` but received `number`",
200+
},
201+
],
202+
schema: testSchemaArrayOfObjects,
203+
},
204+
{
205+
name: "can handle an array of objects",
206+
mode: MODES.JSON,
207+
doc: '[{"name": "John"}, {"name": "Jane"}]',
208+
errors: [],
209+
schema: testSchemaArrayOfObjects,
210+
},
148211
];
149212
it.each([
150213
...jsonSuite,
@@ -360,7 +423,7 @@ oneOfEg2: 123
360423
doc,
361424
errors.map((error) => [error.from, error.to, error.message]),
362425
mode,
363-
schema
426+
schema,
364427
);
365428
});
366429
});

src/features/validation.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export class JSONValidation {
8585
if (error.code === "one-of-error" && errors?.length) {
8686
return `Expected one of ${joinWithOr(
8787
errors,
88-
(data) => data.data.expected
88+
(data) => data.data.expected,
8989
)}`;
9090
}
9191
if (error.code === "type-error") {
@@ -119,6 +119,8 @@ export class JSONValidation {
119119
if (!text?.length) return [];
120120

121121
const json = this.parser(view.state);
122+
// skip validation if parsing fails
123+
if (json.data == null) return [];
122124

123125
let errors: JsonError[] = [];
124126
try {
@@ -147,7 +149,8 @@ export class JSONValidation {
147149
const pointer = json.pointers.get(errorPath) as JSONPointerData;
148150
if (
149151
error.name === "MaxPropertiesError" ||
150-
error.name === "MinPropertiesError"
152+
error.name === "MinPropertiesError" ||
153+
errorPath === "" // root level type errors
151154
) {
152155
pushRoot();
153156
} else if (pointer) {

0 commit comments

Comments
 (0)