Read schema from file
This commit is contained in:
@@ -3,21 +3,25 @@ import * as path from 'path';
|
||||
import { cloneDbConfig, DbConfig } from './db-config';
|
||||
import * as chokidar from 'chokidar';
|
||||
import { DisposableObject } from '../pure/disposable-object';
|
||||
import { validateDbConfig } from './db-config-validation';
|
||||
import { DbConfigValidator } from './db-config-validator';
|
||||
|
||||
export class DbConfigStore extends DisposableObject {
|
||||
private readonly configPath: string;
|
||||
private readonly configValidator: DbConfigValidator;
|
||||
|
||||
private config: DbConfig;
|
||||
private configWatcher: chokidar.FSWatcher | undefined;
|
||||
|
||||
public constructor(workspaceStoragePath: string) {
|
||||
public constructor(
|
||||
workspaceStoragePath: string,
|
||||
extensionPath: string) {
|
||||
super();
|
||||
|
||||
this.configPath = path.join(workspaceStoragePath, 'workspace-databases.json');
|
||||
|
||||
this.config = this.createEmptyConfig();
|
||||
this.configWatcher = undefined;
|
||||
this.configValidator = new DbConfigValidator(extensionPath);
|
||||
}
|
||||
|
||||
public async initialize(): Promise<void> {
|
||||
@@ -34,8 +38,12 @@ export class DbConfigStore extends DisposableObject {
|
||||
return cloneDbConfig(this.config);
|
||||
}
|
||||
|
||||
public getConfigPath(): string {
|
||||
return this.configPath;
|
||||
}
|
||||
|
||||
public validateConfig(): string[] {
|
||||
return validateDbConfig(this.config);
|
||||
return this.configValidator.validate(this.config);
|
||||
}
|
||||
|
||||
private async loadConfig(): Promise<void> {
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
import { DbConfig } from './db-config';
|
||||
import Ajv from 'ajv';
|
||||
|
||||
export function validateDbConfig(dbConfig: DbConfig): string[] {
|
||||
const ajv = new Ajv({ allErrors: true });
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
remote: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
repositoryLists: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string'
|
||||
},
|
||||
repositories: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
pattern: '^[a-zA-Z0-9-_\\.]+/[a-zA-Z0-9-_\\.]+$'
|
||||
}
|
||||
}
|
||||
},
|
||||
required: ['name', 'repositories'],
|
||||
additionalProperties: false
|
||||
}
|
||||
},
|
||||
owners: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
pattern: '^[a-zA-Z0-9-_\\.]+$'
|
||||
}
|
||||
},
|
||||
repositories: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
pattern: '^[a-zA-Z0-9-_\\.]+/[a-zA-Z0-9-_\\.]+$'
|
||||
}
|
||||
}
|
||||
},
|
||||
required: ['repositoryLists', 'owners', 'repositories'],
|
||||
additionalProperties: false
|
||||
}
|
||||
},
|
||||
required: ['remote'],
|
||||
additionalProperties: false
|
||||
};
|
||||
|
||||
ajv.validate(schema, dbConfig);
|
||||
|
||||
if (ajv.errors) {
|
||||
return ajv.errors.map((error) => `${error.instancePath} ${error.message}`);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
24
extensions/ql-vscode/src/databases/db-config-validator.ts
Normal file
24
extensions/ql-vscode/src/databases/db-config-validator.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import * as fs from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
import Ajv from 'ajv';
|
||||
import { DbConfig } from './db-config';
|
||||
|
||||
export class DbConfigValidator {
|
||||
private readonly schema: any;
|
||||
|
||||
constructor(extensionPath: string) {
|
||||
const schemaPath = path.resolve(extensionPath, 'workspace-databases-schema.json');
|
||||
this.schema = fs.readJsonSync(schemaPath);
|
||||
}
|
||||
|
||||
public validate(dbConfig: DbConfig): string[] {
|
||||
const ajv = new Ajv({ allErrors: true });
|
||||
ajv.validate(this.schema, dbConfig);
|
||||
|
||||
if (ajv.errors) {
|
||||
return ajv.errors.map((error) => `${error.instancePath} ${error.message}`);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,8 @@ export class DbModule extends DisposableObject {
|
||||
void logger.log('Initializing database module');
|
||||
|
||||
const storagePath = extensionContext.storageUri?.fsPath || extensionContext.globalStorageUri.fsPath;
|
||||
const dbConfigStore = new DbConfigStore(storagePath);
|
||||
const extensionPath = extensionContext.extensionPath;
|
||||
const dbConfigStore = new DbConfigStore(storagePath, extensionPath);
|
||||
await dbConfigStore.initialize();
|
||||
|
||||
const dbManager = new DbManager(dbConfigStore);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { DbConfigStore } from '../../../src/databases/db-config-store';
|
||||
import { expect } from 'chai';
|
||||
|
||||
describe('db config store', async () => {
|
||||
const extensionPath = path.join(__dirname, '../../..');
|
||||
const tempWorkspaceStoragePath = path.join(__dirname, 'test-workspace');
|
||||
const testDataStoragePath = path.join(__dirname, 'data');
|
||||
|
||||
@@ -18,7 +19,7 @@ describe('db config store', async () => {
|
||||
it('should create a new config if one does not exist', async () => {
|
||||
const configPath = path.join(tempWorkspaceStoragePath, 'workspace-databases.json');
|
||||
|
||||
const configStore = new DbConfigStore(tempWorkspaceStoragePath);
|
||||
const configStore = new DbConfigStore(tempWorkspaceStoragePath, extensionPath);
|
||||
await configStore.initialize();
|
||||
|
||||
expect(await fs.pathExists(configPath)).to.be.true;
|
||||
@@ -29,7 +30,7 @@ describe('db config store', async () => {
|
||||
});
|
||||
|
||||
it('should load an existing config', async () => {
|
||||
const configStore = new DbConfigStore(testDataStoragePath);
|
||||
const configStore = new DbConfigStore(testDataStoragePath, extensionPath);
|
||||
await configStore.initialize();
|
||||
|
||||
const config = configStore.getConfig();
|
||||
@@ -44,7 +45,7 @@ describe('db config store', async () => {
|
||||
});
|
||||
|
||||
it('should not allow modification of the config', async () => {
|
||||
const configStore = new DbConfigStore(testDataStoragePath);
|
||||
const configStore = new DbConfigStore(testDataStoragePath, extensionPath);
|
||||
await configStore.initialize();
|
||||
|
||||
const config = configStore.getConfig();
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { expect } from 'chai';
|
||||
import { validateDbConfig } from '../../../src/databases/db-config-validation';
|
||||
import * as path from 'path';
|
||||
import { DbConfig } from '../../../src/databases/db-config';
|
||||
import { DbConfigValidator } from '../../../src/databases/db-config-validator';
|
||||
|
||||
describe('db config validation', async () => {
|
||||
const extensionPath = path.join(__dirname, '../../..');
|
||||
const configValidator = new DbConfigValidator(extensionPath);
|
||||
|
||||
it('should return error when file is not valid', async () => {
|
||||
// We're intentionally bypassing the type check because we'd
|
||||
// like to make sure validation errors are highlighted.
|
||||
@@ -19,7 +23,7 @@ describe('db config validation', async () => {
|
||||
}
|
||||
} as any as DbConfig;
|
||||
|
||||
const validationOutput = validateDbConfig(dbConfig);
|
||||
const validationOutput = configValidator.validate(dbConfig);
|
||||
|
||||
expect(validationOutput).to.have.length(2);
|
||||
|
||||
Reference in New Issue
Block a user