Skip to content

Commit 03f5859

Browse files
authored
Merge pull request #1717 from github/norascheuch/add-config-validation
Add config validation
2 parents a24e7c6 + 03bc63c commit 03f5859

File tree

7 files changed

+81
-25
lines changed

7 files changed

+81
-25
lines changed

extensions/ql-vscode/package-lock.json

Lines changed: 7 additions & 19 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

extensions/ql-vscode/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1299,6 +1299,7 @@
12991299
"@primer/react": "^35.0.0",
13001300
"@vscode/codicons": "^0.0.31",
13011301
"@vscode/webview-ui-toolkit": "^1.0.1",
1302+
"ajv": "^8.11.0",
13021303
"child-process-promise": "^2.2.1",
13031304
"chokidar": "^3.5.3",
13041305
"classnames": "~2.2.6",
@@ -1389,7 +1390,6 @@
13891390
"@typescript-eslint/eslint-plugin": "^4.26.0",
13901391
"@typescript-eslint/parser": "^4.26.0",
13911392
"@vscode/test-electron": "^2.2.0",
1392-
"ajv": "^8.11.0",
13931393
"ansi-colors": "^4.1.1",
13941394
"applicationinsights": "^2.3.5",
13951395
"babel-loader": "^8.2.5",

extensions/ql-vscode/src/databases/db-config-store.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,25 @@ import * as path from 'path';
33
import { cloneDbConfig, DbConfig } from './db-config';
44
import * as chokidar from 'chokidar';
55
import { DisposableObject } from '../pure/disposable-object';
6+
import { DbConfigValidator } from './db-config-validator';
67

78
export class DbConfigStore extends DisposableObject {
89
private readonly configPath: string;
10+
private readonly configValidator: DbConfigValidator;
911

1012
private config: DbConfig;
1113
private configWatcher: chokidar.FSWatcher | undefined;
1214

13-
public constructor(workspaceStoragePath: string) {
15+
public constructor(
16+
workspaceStoragePath: string,
17+
extensionPath: string) {
1418
super();
1519

1620
this.configPath = path.join(workspaceStoragePath, 'workspace-databases.json');
1721

1822
this.config = this.createEmptyConfig();
1923
this.configWatcher = undefined;
24+
this.configValidator = new DbConfigValidator(extensionPath);
2025
}
2126

2227
public async initialize(): Promise<void> {
@@ -37,6 +42,10 @@ export class DbConfigStore extends DisposableObject {
3742
return this.configPath;
3843
}
3944

45+
public validateConfig(): string[] {
46+
return this.configValidator.validate(this.config);
47+
}
48+
4049
private async loadConfig(): Promise<void> {
4150
if (!await fs.pathExists(this.configPath)) {
4251
await fs.writeJSON(this.configPath, this.createEmptyConfig(), { spaces: 2 });
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import * as fs from 'fs-extra';
2+
import * as path from 'path';
3+
import Ajv from 'ajv';
4+
import { DbConfig } from './db-config';
5+
6+
export class DbConfigValidator {
7+
private readonly schema: any;
8+
9+
constructor(extensionPath: string) {
10+
const schemaPath = path.resolve(extensionPath, 'workspace-databases-schema.json');
11+
this.schema = fs.readJsonSync(schemaPath);
12+
}
13+
14+
public validate(dbConfig: DbConfig): string[] {
15+
const ajv = new Ajv({ allErrors: true });
16+
ajv.validate(this.schema, dbConfig);
17+
18+
if (ajv.errors) {
19+
return ajv.errors.map((error) => `${error.instancePath} ${error.message}`);
20+
}
21+
22+
return [];
23+
}
24+
}

extensions/ql-vscode/src/databases/db-module.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ export class DbModule extends DisposableObject {
2222
void logger.log('Initializing database module');
2323

2424
const storagePath = extensionContext.storageUri?.fsPath || extensionContext.globalStorageUri.fsPath;
25-
const dbConfigStore = new DbConfigStore(storagePath);
25+
const extensionPath = extensionContext.extensionPath;
26+
const dbConfigStore = new DbConfigStore(storagePath, extensionPath);
2627
await dbConfigStore.initialize();
2728

2829
const dbManager = new DbManager(dbConfigStore);

extensions/ql-vscode/test/pure-tests/databases/db-config-store.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { DbConfigStore } from '../../../src/databases/db-config-store';
44
import { expect } from 'chai';
55

66
describe('db config store', async () => {
7+
const extensionPath = path.join(__dirname, '../../..');
78
const tempWorkspaceStoragePath = path.join(__dirname, 'test-workspace');
89
const testDataStoragePath = path.join(__dirname, 'data');
910

@@ -18,7 +19,7 @@ describe('db config store', async () => {
1819
it('should create a new config if one does not exist', async () => {
1920
const configPath = path.join(tempWorkspaceStoragePath, 'workspace-databases.json');
2021

21-
const configStore = new DbConfigStore(tempWorkspaceStoragePath);
22+
const configStore = new DbConfigStore(tempWorkspaceStoragePath, extensionPath);
2223
await configStore.initialize();
2324

2425
expect(await fs.pathExists(configPath)).to.be.true;
@@ -29,7 +30,7 @@ describe('db config store', async () => {
2930
});
3031

3132
it('should load an existing config', async () => {
32-
const configStore = new DbConfigStore(testDataStoragePath);
33+
const configStore = new DbConfigStore(testDataStoragePath, extensionPath);
3334
await configStore.initialize();
3435

3536
const config = configStore.getConfig();
@@ -44,7 +45,7 @@ describe('db config store', async () => {
4445
});
4546

4647
it('should not allow modification of the config', async () => {
47-
const configStore = new DbConfigStore(testDataStoragePath);
48+
const configStore = new DbConfigStore(testDataStoragePath, extensionPath);
4849
await configStore.initialize();
4950

5051
const config = configStore.getConfig();
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { expect } from 'chai';
2+
import * as path from 'path';
3+
import { DbConfig } from '../../../src/databases/db-config';
4+
import { DbConfigValidator } from '../../../src/databases/db-config-validator';
5+
6+
describe('db config validation', async () => {
7+
const extensionPath = path.join(__dirname, '../../..');
8+
const configValidator = new DbConfigValidator(extensionPath);
9+
10+
it('should return error when file is not valid', async () => {
11+
// We're intentionally bypassing the type check because we'd
12+
// like to make sure validation errors are highlighted.
13+
const dbConfig = {
14+
'remote': {
15+
'repositoryLists': [
16+
{
17+
'name': 'repoList1',
18+
'repositories': ['foo/bar', 'foo/baz']
19+
}
20+
],
21+
'repositories': ['owner/repo1', 'owner/repo2', 'owner/repo3'],
22+
'somethingElse': 'bar'
23+
}
24+
} as any as DbConfig;
25+
26+
const validationOutput = configValidator.validate(dbConfig);
27+
28+
expect(validationOutput).to.have.length(2);
29+
30+
expect(validationOutput[0]).to.deep.equal('/remote must have required property \'owners\'');
31+
expect(validationOutput[1]).to.deep.equal('/remote must NOT have additional properties');
32+
});
33+
});

0 commit comments

Comments
 (0)