Skip to content

Commit f5d85d9

Browse files
committed
Support schema in YAML
1 parent 61a9c38 commit f5d85d9

14 files changed

+291
-99
lines changed

README.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# Hyperjump - JSON Schema Test Coverage
22

33
This package provides tools for testing JSON Schemas and providing test coverage
4-
for schema files in your code base. Integration is provided for Vitest, but the
5-
component for collecting the coverage data is also exposed if you want to
6-
do some other integration.
4+
for schema files in JSON or YAML in your code base. Integration is provided for
5+
Vitest, but the component for collecting the coverage data is also exposed if
6+
you want to do some other integration.
77

88
Validation is done by `@hyperjump/json-schema`, so you can use any version of
99
JSON Schema supported by that package.
@@ -28,9 +28,7 @@ All files | 81.81 | 66.66 | 80 | 88.88 |
2828

2929
The following are known limitations I'm hopeful can be addressed.
3030

31-
- Coverage can only be reported for `**/*.schema.json` and `**/schema.json`
32-
files.
33-
- Schemas in YAML aren't supported.
31+
- Coverage can only be reported for `*.schema.(json|yaml|yml)` files.
3432

3533
## Vitest
3634

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
},
2929
"devDependencies": {
3030
"@stylistic/eslint-plugin": "*",
31+
"@types/content-type": "^1.1.9",
3132
"@types/istanbul-lib-coverage": "^2.0.6",
3233
"@types/istanbul-reports": "^3.0.4",
3334
"@types/moo": "^0.5.10",
@@ -42,13 +43,16 @@
4243
"dependencies": {
4344
"@hyperjump/json-schema": "^1.16.0",
4445
"@hyperjump/uri": "^1.3.1",
46+
"content-type": "^1.0.5",
4547
"ignore": "^7.0.5",
4648
"istanbul-lib-coverage": "^3.2.2",
4749
"istanbul-lib-report": "^3.0.1",
4850
"istanbul-reports": "^3.1.7",
4951
"moo": "^0.5.2",
5052
"pathe": "^2.0.3",
5153
"tinyglobby": "^0.2.14",
52-
"vfile": "^6.0.3"
54+
"vfile": "^6.0.3",
55+
"yaml": "^2.8.0",
56+
"yaml-unist-parser": "^2.0.5"
5357
}
5458
}

src/coverage-util.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import type { CompiledSchema } from "@hyperjump/json-schema/experimental";
2+
import type { CoverageMapData } from "istanbul-lib-coverage";
3+
import type { JsonNode } from "./jsonast.js";
4+
5+
export const astToCoverageMap: (compiledSchema: CompiledSchema, schemaPath: string, schemaNodes: Record<string, JsonNode>) => CoverageMapData;
6+
7+
export const registerSchema: (filePath: string) => Promise<void>;
8+
export const parseToAst: (schemaPath: string) => Promise<JsonNode>;

src/coverage-util.js

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
1+
import { readFile } from "node:fs/promises";
2+
import { extname } from "node:path";
3+
import YAML from "yaml";
14
import { getKeyword } from "@hyperjump/json-schema/experimental";
25
import { parseIri, toAbsoluteIri } from "@hyperjump/uri";
3-
import { getNodeFromPointer } from "./json-util.js";
6+
import { registerSchema as register } from "./json-schema.js";
7+
import { fromJson, fromYaml, getNodeFromPointer } from "./json-util.js";
48

59
/**
610
* @import { Position } from "unist"
7-
* @import { CompiledSchema } from "@hyperjump/json-schema/experimental"
8-
* @import { CoverageMapData, FileCoverageData, Range } from "istanbul-lib-coverage"
9-
* @import { JsonNode } from "./jsonast.js"
11+
* @import { FileCoverageData, Range } from "istanbul-lib-coverage"
12+
* @import { SchemaObject } from "@hyperjump/json-schema"
13+
* @import { JsonNode } from "./jsonast.d.ts"
14+
* @import * as API from "./coverage-util.d.ts"
1015
*/
1116

12-
/** @type (compiledSchema: CompiledSchema, schemaPath: string, schemaNodes: Record<string, JsonNode>) => CoverageMapData */
17+
/** @type API.astToCoverageMap */
1318
export const astToCoverageMap = (compiledSchema, schemaPath, schemaNodes) => {
1419
/** @type FileCoverageData */
1520
const fileCoverage = {
@@ -109,3 +114,43 @@ const annotationKeywords = new Set([
109114
"https://json-schema.org/keyword/examples",
110115
"https://json-schema.org/keyword/format"
111116
]);
117+
118+
/** @type API.registerSchema */
119+
export const registerSchema = async (schemaPath) => {
120+
const text = await readFile(schemaPath, "utf-8");
121+
const extension = extname(schemaPath);
122+
123+
/** @type SchemaObject | boolean */
124+
let schema;
125+
switch (extension) {
126+
case ".json":
127+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
128+
schema = JSON.parse(text);
129+
break;
130+
case ".yaml":
131+
case ".yml":
132+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
133+
schema = YAML.parse(text);
134+
break;
135+
default:
136+
throw Error(`File of type '${extension}' is not supported.`);
137+
}
138+
139+
register(schema);
140+
};
141+
142+
/** @type API.parseToAst */
143+
export const parseToAst = async (schemaPath) => {
144+
const text = await readFile(schemaPath, "utf-8");
145+
const extension = extname(schemaPath);
146+
147+
switch (extension) {
148+
case ".json":
149+
return fromJson(text);
150+
case ".yaml":
151+
case ".yml":
152+
return fromYaml(text);
153+
default:
154+
throw Error(`File of type '${extension}' is not supported.`);
155+
}
156+
};

src/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export { TestCoverageEvaluationPlugin } from "./test-coverage-evaluation-plugin.d.ts";
2+
export * from "./coverage-util.d.ts";

src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export { TestCoverageEvaluationPlugin } from "./test-coverage-evaluation-plugin.js";
2+
export * from "./coverage-util.js";

src/json-schema.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import "@hyperjump/json-schema/draft-2020-12";
2+
import "@hyperjump/json-schema/draft-2019-09";
3+
import "@hyperjump/json-schema/draft-07";
4+
import "@hyperjump/json-schema/draft-06";
5+
import "@hyperjump/json-schema/draft-04";
6+
import "@hyperjump/json-schema/openapi-3-0";
7+
import "@hyperjump/json-schema/openapi-3-1";
8+
import { buildSchemaDocument } from "@hyperjump/json-schema/experimental";
9+
import { addMediaTypePlugin } from "@hyperjump/browser";
10+
import contentTypeParser from "content-type";
11+
import YAML from "yaml";
12+
13+
/**
14+
* @import { SchemaObject } from "@hyperjump/json-schema"
15+
*/
16+
17+
addMediaTypePlugin("application/schema+json", {
18+
parse: async (response) => {
19+
const contentType = contentTypeParser.parse(response.headers.get("content-type") ?? "");
20+
const contextDialectId = contentType.parameters.schema ?? contentType.parameters.profile;
21+
22+
/** @type SchemaObject */
23+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
24+
const json = await response.json();
25+
return buildSchemaDocument(json, response.url, contextDialectId);
26+
},
27+
// eslint-disable-next-line @typescript-eslint/require-await
28+
fileMatcher: async (path) => /\.json$/i.test(path)
29+
});
30+
31+
addMediaTypePlugin("application/schema+yaml", {
32+
parse: async (response) => {
33+
const contentType = contentTypeParser.parse(response.headers.get("content-type") ?? "");
34+
const contextDialectId = contentType.parameters.schema ?? contentType.parameters.profile;
35+
36+
/** @type SchemaObject */
37+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
38+
const yaml = YAML.parse(await response.text());
39+
return buildSchemaDocument(yaml, response.url, contextDialectId);
40+
},
41+
// eslint-disable-next-line @typescript-eslint/require-await
42+
fileMatcher: async (path) => /\.ya?ml$/i.test(path)
43+
});
44+
45+
export * from "@hyperjump/json-schema/draft-2020-12";

0 commit comments

Comments
 (0)