Use generated schema to validate qlpack file

This commit is contained in:
Koen Vlaswinkel
2024-01-02 16:19:11 +01:00
parent e81f1a1fde
commit 4129962fa0
8 changed files with 153 additions and 22 deletions

View File

@@ -6,6 +6,16 @@ import { format, resolveConfig } from "prettier";
const extensionDirectory = resolve(__dirname, "..");
const schemas = [
{
path: join(extensionDirectory, "src", "packaging", "qlpack-file.ts"),
type: "QlPackFile",
schemaPath: join(
extensionDirectory,
"src",
"packaging",
"qlpack-file.schema.json",
),
},
{
path: join(
extensionDirectory,

View File

@@ -1,7 +1,5 @@
import { load } from "js-yaml";
import { readFile } from "fs-extra";
import { QlPackFile } from "../packaging/qlpack-file";
import { QueryLanguage } from "./query-language";
import { loadQlpackFile } from "../packaging/qlpack-file-loader";
/**
* @param qlpackPath The path to the `qlpack.yml` or `codeql-pack.yml` file.
@@ -11,11 +9,9 @@ import { QueryLanguage } from "./query-language";
export async function getQlPackLanguage(
qlpackPath: string,
): Promise<QueryLanguage | undefined> {
const qlPack = load(await readFile(qlpackPath, "utf8")) as
| QlPackFile
| undefined;
const qlPack = await loadQlpackFile(qlpackPath);
const dependencies = qlPack?.dependencies;
if (!dependencies || typeof dependencies !== "object") {
if (!dependencies) {
return;
}

View File

@@ -5,6 +5,12 @@
"ExtensionPackMetadata": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"extensionTargets": {
"type": "object",
"additionalProperties": {
@@ -24,12 +30,6 @@
}
]
},
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"dependencies": {
"type": "object",
"additionalProperties": {

View File

@@ -1,7 +1,9 @@
import { QlPackFile } from "../packaging/qlpack-file";
export type ExtensionPackMetadata = QlPackFile & {
// Make both extensionTargets and dataExtensions required
// Make name, version, extensionTargets, and dataExtensions required
name: string;
version: string;
extensionTargets: Record<string, string>;
dataExtensions: string[] | string;
};

View File

@@ -0,0 +1,28 @@
import Ajv from "ajv";
import * as qlpackFileSchemaJson from "./qlpack-file.schema.json";
import { QlPackFile } from "./qlpack-file";
import { load } from "js-yaml";
import { readFile } from "fs-extra";
const ajv = new Ajv({ allErrors: true });
const qlpackFileValidate = ajv.compile(qlpackFileSchemaJson);
export async function loadQlpackFile(path: string): Promise<QlPackFile> {
const qlPack = load(await readFile(path, "utf8")) as QlPackFile | undefined;
qlpackFileValidate(qlPack);
if (qlpackFileValidate.errors) {
throw new Error(
`Invalid extension pack YAML: ${qlpackFileValidate.errors
.map((error) => `${error.instancePath} ${error.message}`)
.join(", ")}`,
);
}
if (!qlPack) {
throw new Error(`Could not parse ${path}`);
}
return qlPack;
}

View File

@@ -0,0 +1,97 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$ref": "#/definitions/QlPackFile",
"definitions": {
"QlPackFile": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"dependencies": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"extensionTargets": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"dbscheme": {
"type": "string"
},
"library": {
"type": "boolean"
},
"defaultSuite": {
"type": "array",
"items": {
"$ref": "#/definitions/SuiteInstruction"
}
},
"defaultSuiteFile": {
"type": "string"
},
"dataExtensions": {
"anyOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "string"
}
]
}
},
"description": "The qlpack pack file, either in qlpack.yml or in codeql-pack.yml."
},
"SuiteInstruction": {
"type": "object",
"properties": {
"qlpack": {
"type": "string"
},
"query": {
"type": "string"
},
"queries": {
"type": "string"
},
"include": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
},
"exclude": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
},
"description": {
"type": "string"
},
"from": {
"type": "string"
}
},
"description": "A single entry in a .qls file."
}
}
}

View File

@@ -4,8 +4,8 @@ import { SuiteInstruction } from "./suite-instruction";
* The qlpack pack file, either in qlpack.yml or in codeql-pack.yml.
*/
export interface QlPackFile {
name: string;
version: string;
name?: string;
version?: string;
dependencies?: Record<string, string>;
extensionTargets?: Record<string, string>;
dbscheme?: string;

View File

@@ -59,24 +59,22 @@ describe("getQlPackLanguage", () => {
expect(result).toEqual(undefined);
});
it("should find nothing when dependencies is a scalar", async () => {
it("should throw when dependencies is a scalar", async () => {
await writeYAML(qlpackPath, {
name: "test",
dependencies: "codeql/java-all",
});
const result = await getQlPackLanguage(qlpackPath);
expect(result).toEqual(undefined);
await expect(getQlPackLanguage(qlpackPath)).rejects.toBeDefined();
});
it("should find nothing when dependencies is an array", async () => {
it("should throw when dependencies is an array", async () => {
await writeYAML(qlpackPath, {
name: "test",
dependencies: ["codeql/java-all"],
});
const result = await getQlPackLanguage(qlpackPath);
expect(result).toEqual(undefined);
await expect(getQlPackLanguage(qlpackPath)).rejects.toBeDefined();
});
it("should find nothing when there are no matching dependencies", async () => {