Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Allow schema to be free-form #80 #162

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"debounce-fn": "^4.0.0",
"dot-prop": "^6.0.1",
"env-paths": "^2.2.1",
"json-schema-typed": "^7.0.3",
"json-schema-typed": "^8.0.0",
"onetime": "^5.1.2",
"pkg-up": "^3.1.0",
"semver": "^7.3.5"
Expand Down
22 changes: 18 additions & 4 deletions source/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,17 @@ import debounceFn = require('debounce-fn');
import semver = require('semver');
import onetime = require('onetime');
import {JSONSchema} from 'json-schema-typed';
import {Deserialize, Migrations, OnDidChangeCallback, Options, Serialize, Unsubscribe, Schema, OnDidAnyChangeCallback} from './types';
import {
Deserialize,
Migrations,
OnDidChangeCallback,
Options,
Serialize,
Unsubscribe,
Schema,
OnDidAnyChangeCallback,
ObjectSchema
} from './types';

const encryptionAlgorithm = 'aes-256-cbc';

Expand Down Expand Up @@ -49,6 +59,10 @@ const checkValueType = (key: string, value: unknown): void => {
}
};

const isObjectJSONSchema = <T>(schema: Schema<T>): schema is ObjectSchema => {
return typeof schema === 'object' && 'type' in schema && schema.type === 'object';
};

const INTERNAL_KEY = '__internal__';
const MIGRATION_KEY = `${INTERNAL_KEY}.migrations.version`;

Expand Down Expand Up @@ -105,15 +119,15 @@ class Conf<T extends Record<string, any> = Record<string, unknown>> implements I
});
ajvFormats(ajv);

const schema: JSONSchema = {
const schema: JSONSchema = isObjectJSONSchema(options.schema) ? options.schema : {
type: 'object',
properties: options.schema
};

this.#validator = ajv.compile(schema);

for (const [key, value] of Object.entries<JSONSchema>(options.schema)) {
if (value?.default) {
for (const [key, value] of Object.entries<JSONSchema>(schema.properties!)) {
if (value && typeof value === 'object' && value.default) {
this.#defaultValues[key as keyof T] = value.default;
}
}
Expand Down
5 changes: 3 additions & 2 deletions source/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,9 @@ export interface Options<T> {

export type Migrations<T> = Record<string, (store: Conf<T>) => void>;

export type Schema<T> = {[Property in keyof T]: ValueSchema};
export type ValueSchema = TypedJSONSchema;
export type Schema<T> = ObjectSchema | {[Property in keyof T]: ValueSchema<T[Property]>};
export type ObjectSchema = TypedJSONSchema<Record<string | number, unknown>>;
export type ValueSchema<T> = TypedJSONSchema<T>;

export type Serialize<T> = (value: T) => string;
export type Deserialize<T> = (text: string) => T;
Expand Down
30 changes: 27 additions & 3 deletions test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,30 @@ test('schema - valid set', t => {
});
});

test('schema - valid set with full schema', t => {
const schema: Schema<{foo: {bar: number; foobar: number}}> = {
type: 'object',
sindresorhus marked this conversation as resolved.
Show resolved Hide resolved
properties: {
foo: {
type: 'object',
properties: {
bar: {
type: 'number'
},
foobar: {
type: 'number',
maximum: 100
}
}
}
}
sindresorhus marked this conversation as resolved.
Show resolved Hide resolved
};
const config = new Conf({cwd: tempy.directory(), schema});
t.notThrows(() => {
config.set('foo', {bar: 1, foobar: 2});
});
});

test('schema - one violation', t => {
const config = new Conf({
cwd: tempy.directory(),
Expand Down Expand Up @@ -835,11 +859,11 @@ test('schema - validate Conf default', t => {
new Conf({
cwd: tempy.directory(),
defaults: {
// For our tests to fail and typescript to compile, we'll ignore this ts error.
// This error is not bad and means the package is well typed.
// @ts-expect-error
foo: 1
},
// For our tests to fail and typescript to compile, we'll ignore this ts error.
// This error is not bad and means the package is well typed.
// @ts-expect-error
schema
});
}, {message: 'Config schema violation: `foo` must be string'});
Expand Down