From 0408065886b150e3814b0c96e6601e2c541ed5ea Mon Sep 17 00:00:00 2001 From: Diego Magdaleno Date: Sat, 22 May 2021 10:47:11 -0500 Subject: [PATCH] Config: Refactor config to be be in separate classes --- src/util/Config.ts | 174 +++++++++++++++++++++++++-------------------- 1 file changed, 95 insertions(+), 79 deletions(-) diff --git a/src/util/Config.ts b/src/util/Config.ts index 9b2b9600..1e29508b 100644 --- a/src/util/Config.ts +++ b/src/util/Config.ts @@ -368,23 +368,91 @@ const createPlainObject = (): T => { type Serialize = (value: T) => string; type Deserialize = (text: string) => T; +function getConfigPath(): string { + const configEnvPath = envPaths('fosscord', {suffix: ""}).config; + const configPath = path.resolve(configEnvPath, 'api.json'); + return configPath +} -class Config = Record> implements Iterable<[keyof T, T[keyof T]]> { + +class Store = Record> implements Iterable<[keyof T, T[keyof T]]>{ readonly path: string; - readonly #validator?: ValidateFunction; - readonly #defaultOptions: Partial = {}; + readonly validator: ValidateFunction; + constructor(path: string, validator: ValidateFunction) { + this.validator = validator; + if (fs.existsSync(path)) { + this.path = path + } else { + this._ensureDirectory() + } + } - constructor() { + private _ensureDirectory(): void { + fs.mkdirSync(path.dirname(this.path), {recursive: true}) + } - const ajv = new Ajv(); + protected _validate(data: T | unknown): void { + const valid = this.validator(data); + if (valid || !this.validator.errors) { + return; + } - ajvFormats(ajv); + const errors = this.validator.errors.map(({ instancePath, message = '' }) => `\`${instancePath.slice(1)}\` ${message}`); + throw new Error('The config schema was violated!: ' + errors.join('; ')); + } - this.#validator = ajv.compile(schema); + private _write(value: T): void { + let data: string | Buffer = this._serialize(value); - const base = envPaths('fosscord', {suffix: ""}).config; + try { + atomically.writeFileSync(this.path, data); + } catch (error) { + throw error; + } + } - this.path = path.resolve(base, 'api.json'); + private readonly _serialize: Serialize = value => JSON.stringify(value, undefined, '\t'); + private readonly _deserialize: Deserialize = value => JSON.parse(value); + + public get store(): T { + try { + const data = fs.readFileSync(this.path).toString(); + const deserializedData = this._deserialize(data); + this._validate(deserializedData); + return Object.assign(Object.create(null), deserializedData) + } catch (error) { + if (error.code == 'ENOENT') { + this._ensureDirectory(); + return Object.create(null); + } + + throw error; + } + } + + public set store(value: T) { + this._validate(value); + + this._write(value); + } + + *[Symbol.iterator](): IterableIterator<[keyof T, T[keyof T]]>{ + for (const [key, value] of Object.entries(this.store)) { + yield [key, value] + } + } +} + +interface Options { + path: string; + schemaValidator: ValidateFunction; +} + +class Config = Record> extends Store implements Iterable<[keyof T, T[keyof T]]> { + readonly path: string; + + constructor(options: Readonly> = {}) { + super(options.path!, options.schemaValidator!); const fileStore = this.store; @@ -398,68 +466,24 @@ class Config = Record> implements } } + public get(key: Key): T[Key]; + public get(key: Key, defaultValue: Required[Key]): Required[Key]; + public get(key: Exclude, defaultValue?: Value): Value; + public get(key: string, defaultValue?: unknown): unknown { + return this._get(key, defaultValue); + } + private _has(key: Key | string): boolean { return dotProp.has(this.store, key as string); } - private _validate(data: T | unknown): void { - if (!this.#validator) { - return; - } - - const valid = this.#validator(data); - if (valid || !this.#validator.errors) { - return; - } - - const errors = this.#validator.errors.map(({ instancePath, message = '' }) => `\`${instancePath.slice(1)}\` ${message}`); - throw new Error('The config schema was violated!: ' + errors.join('; ')); - } - - get store(): T { - try { - const data = fs.readFileSync(this.path).toString(); - const deserializedData = this._deserialize(data); - this._validate(deserializedData); - return Object.assign(Object.create(null), deserializedData) - } catch (error) { - if (error.code == 'ENOENT') { - this._ensureDirectory(); - return createPlainObject(); - - } - - throw error; - } - } - - private _ensureDirectory(): void { - fs.mkdirSync(path.dirname(this.path), { recursive: true }) - } - - set store(value: T) { - this._validate(value); - - this._write(value); - } - - private readonly _deserialize: Deserialize = value => JSON.parse(value); - private readonly _serialize: Serialize = value => JSON.stringify(value, undefined, '\t') - - get(key: Key): T[Key]; - get(key: Key, defaultValue: Required[Key]): Required[Key]; - get(key: Exclude, defaultValue?: Value): Value; - get(key: string, defaultValue?: unknown): unknown { - return this._get(key, defaultValue); - } - public getAll(): DefaultOptions { return this.store as unknown as DefaultOptions } - private _get(key: Key): T[Key] | undefined; - private _get(key: Key, defaultValue: Default): T[Key] | Default; - private _get(key: Key | string, defaultValue?: Default): Default | undefined { + _get(key: Key): T[Key] | undefined; + _get(key: Key, defaultValue: Default): T[Key] | Default; + _get(key: Key | string, defaultValue?: Default): Default | undefined { if (!this._has(key)) { throw new Error("Tried to acess a non existant property in the config"); } @@ -467,26 +491,18 @@ class Config = Record> implements return dotProp.get(this.store, key as string, defaultValue as T[Key]); } - *[Symbol.iterator](): IterableIterator<[keyof T, T[keyof T]]> { + * [Symbol.iterator](): IterableIterator<[keyof T, T[keyof T]]> { for (const [key, value] of Object.entries(this.store)) { yield [key, value]; } } - private _write(value: T): void { - let data: string | Buffer = this._serialize(value); - - try { - atomically.writeFileSync(this.path, data); - } catch (error) { - if (error.code == 'EXDEV') { - fs.writeFileSync(this.path, data) - return - } - - throw error; - } - } } -export const apiConfig = new Config(); \ No newline at end of file +const ajv = new Ajv(); +const validator = ajv.compile(schema); + +const configPath = getConfigPath() +console.log(configPath) + +export const apiConfig = new Config({path: configPath, schemaValidator: validator}); \ No newline at end of file