Skip to content

Commit

Permalink
feat(shortstop): add unset to unset a value, add formatters to base64…
Browse files Browse the repository at this point in the history
… and config handlers
  • Loading branch information
djMax committed Oct 21, 2023
1 parent 8263436 commit 9ed5ae1
Show file tree
Hide file tree
Showing 5 changed files with 213 additions and 70 deletions.
3 changes: 3 additions & 0 deletions __tests__/defaults/unset.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"override": "unset:"
}
51 changes: 46 additions & 5 deletions src/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,37 @@ import { loadJsonc } from './common';

type IntermediateConfigValue = ReturnType<typeof JSON.parse>;

export async function resolveConfig(
inputData: IntermediateConfigValue,
) {
const filters = {
// Return the variable if it exists and is non-empty
'|u': (value?: unknown) => {
return value === '' ? undefined : value;
},
// Return the variable as a number if it exists, or undefined
'|ud': (value?: unknown) => {
return value === '' || value === undefined || value === null
? undefined
: parseInt(value.toString(), 10);
},
// Return the value as a decimal
'|d': (value?: unknown) => {
return parseInt(value?.toString() || '', 10);
},
// Return the value as a boolean - empty, false, 0 and undefined will be false
'|b': (value?: unknown) => {
return (
value !== '' && value !== 'false' && value !== '0' && value !== undefined && value !== null
);
},
// Return the value as a boolean but inverted so that empty/undefined/0/false are true
'|!b': (value?: unknown) => {
return (
value === '' || value === 'false' || value === '0' || value === undefined || value === null
);
},
'|': (value?: unknown) => value,
};

export async function resolveConfig(inputData: IntermediateConfigValue) {
const shorty = createShortstopHandlers();

let data: unknown = inputData;
Expand All @@ -16,7 +44,17 @@ export async function resolveConfig(
shorty.use('config', (key: string) => {
usedHandler = true;

const keys = key.split('.');
let finalKey = key;
let transform: (value: unknown) => unknown = filters['|'];

Object.entries(filters).some(([filter, fn]) => {
if (key.endsWith(filter)) {
transform = fn;
finalKey = key.slice(0, -filter.length);
}
});

const keys = finalKey.split('.');
let result: unknown = data;

while (result && keys.length) {
Expand All @@ -27,7 +65,10 @@ export async function resolveConfig(
result = (result as Record<string, unknown>)[prop];
}

return keys.length ? null : result;
if (keys.length) {
return null;
}
return transform(result);
});

do {
Expand Down
186 changes: 123 additions & 63 deletions src/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import path from 'path';

import { afterAll, describe, expect, test } from 'vitest';

import { BaseConfitSchema, confit } from './index';
import { BaseConfitSchema, confit, unsetHandler } from './index';

describe('confit', () => {
const originalEnv = process.env.NODE_ENV;
Expand Down Expand Up @@ -36,11 +36,13 @@ describe('confit', () => {
});

test('use', async () => {
const config = await confit<{
foo: { bar: string };
bar: string;
arr: (number | string)[];
} & BaseConfitSchema>().create();
const config = await confit<
{
foo: { bar: string };
bar: string;
arr: (number | string)[];
} & BaseConfitSchema
>().create();
config.use({ foo: { bar: 'baz' } });

expect(typeof config.get().foo).toBe('object');
Expand All @@ -65,18 +67,20 @@ describe('confit', () => {

test('import protocol', async () => {
const basedir = path.join(__dirname, '..', '__tests__', 'import');
const config = await confit<{
name: string;
child: {
const config = await confit<
{
name: string;
grandchild: {
child: {
name: string;
grandchild: {
name: string;
};
grandchildJson: {
name: string;
};
};
grandchildJson: {
name: string;
};
};
} & BaseConfitSchema>({ basedir }).create();
} & BaseConfitSchema
>({ basedir }).create();
expect(config.get().name).toBe('parent');
expect(config.get().child.name).toBe('child');
expect(config.get().child.grandchild.name).toBe('grandchild');
Expand All @@ -98,17 +102,19 @@ describe('confit', () => {

test('config protocol', async () => {
const basedir = path.join(__dirname, '..', '__tests__', 'config');
const config = await confit<{
name: string;
foo: string;
bar: string;
baz: string;
imported: {
const config = await confit<
{
name: string;
foo: string;
};
path: { to: { nested: { value: string } } };
value: string;
} & BaseConfitSchema>({ basedir }).create();
bar: string;
baz: string;
imported: {
foo: string;
};
path: { to: { nested: { value: string } } };
value: string;
} & BaseConfitSchema
>({ basedir }).create();
expect(config.get().name).toBe('config');
expect(config.get().foo).toBe(config.get().imported.foo);
expect(config.get().bar).toBe(config.get().foo);
Expand All @@ -118,16 +124,18 @@ describe('confit', () => {

test('default file import', async () => {
const basedir = path.join(__dirname, '..', '__tests__', 'import');
const config = await confit<{
name: string;
foo: string;
child: {
const config = await confit<
{
name: string;
grandchild: {
foo: string;
child: {
name: string;
grandchild: {
name: string;
};
};
};
} & BaseConfitSchema>({ basedir })
} & BaseConfitSchema
>({ basedir })
.addDefault('./default.json')
.create();
expect(config.get().name).toBe('parent');
Expand Down Expand Up @@ -157,10 +165,12 @@ describe('confit', () => {
process.env.NODE_ENV = 'test';

const basedir = path.join(__dirname, '..', '__tests__', 'defaults');
const config = await confit<{
default: string;
override: string;
} & BaseConfitSchema>({ basedir }).create();
const config = await confit<
{
default: string;
override: string;
} & BaseConfitSchema
>({ basedir }).create();

// File-based overrides
expect(config.get().default).toBe('config');
Expand All @@ -178,10 +188,12 @@ describe('confit', () => {
process.env.NODE_ENV = 'dev';

const basedir = path.join(__dirname, '..', '__tests__', 'defaults');
const config = await confit<{
default: string;
override: string;
} & BaseConfitSchema>({ basedir }).create();
const config = await confit<
{
default: string;
override: string;
} & BaseConfitSchema
>({ basedir }).create();
// File-based overrides
expect(config.get().default).toBe('config');
expect(config.get().override).toBe('development');
Expand All @@ -196,7 +208,9 @@ describe('confit', () => {

test('confit addOverride as json object', async () => {
const basedir = path.join(__dirname, '..', '__tests__', 'config');
const config = await confit<{ name: string; foo?: string; tic: { tac: string } } & BaseConfitSchema>({ basedir })
const config = await confit<
{ name: string; foo?: string; tic: { tac: string } } & BaseConfitSchema
>({ basedir })
.addOverride({
tic: {
tac: 'toe',
Expand All @@ -210,11 +224,13 @@ describe('confit', () => {
});

test('confit without files, using just json objects', async () => {
const config = await confit<{
foo: 'bar';
tic: { tac: string };
blue: boolean;
} & BaseConfitSchema>()
const config = await confit<
{
foo: 'bar';
tic: { tac: string };
blue: boolean;
} & BaseConfitSchema
>()
.addDefault({
foo: 'bar',
tic: {
Expand All @@ -232,10 +248,12 @@ describe('confit', () => {
test('protocols', async () => {
process.env.NODE_ENV = 'dev';
const basedir = path.join(__dirname, '..', '__tests__', 'defaults');
const config = await confit<{
misc: string;
path: string;
} & BaseConfitSchema>({
const config = await confit<
{
misc: string;
path: string;
} & BaseConfitSchema
>({
basedir,
protocols: {
path: (value: string) => path.join(basedir, value),
Expand All @@ -258,10 +276,12 @@ describe('confit', () => {
},
};

const config = await confit<{
misc: string;
path: string;
} & BaseConfitSchema>(options).create();
const config = await confit<
{
misc: string;
path: string;
} & BaseConfitSchema
>(options).create();
expect(config.get().misc).toBe(path.join(basedir, 'config.json!'));
expect(config.get().path).toBe(path.join(basedir, 'development.json!'));

Expand All @@ -288,13 +308,30 @@ describe('confit', () => {
await expect(confit({ basedir }).create()).rejects.toThrow();
});

test('unset', async () => {
const basedir = path.join(__dirname, '..', '__tests__', 'defaults');
const config = await confit<
{
default: string;
override: string;
} & BaseConfitSchema
>({ basedir, protocols: { unset: unsetHandler() } })
.addOverride('development.json')
.addOverride(path.join(basedir, 'unset.json'))
.create();

expect(config.get().override).toBeUndefined();
});

test('addOverride', async () => {
process.env.NODE_ENV = 'test';
const basedir = path.join(__dirname, '..', '__tests__', 'defaults');
const config = await confit<{
default: string;
override: string;
} & BaseConfitSchema>({ basedir })
const config = await confit<
{
default: string;
override: string;
} & BaseConfitSchema
>({ basedir })
.addOverride('development.json')
.addOverride(path.join(basedir, 'supplemental.json'))
.create();
Expand All @@ -314,7 +351,9 @@ describe('confit', () => {
const config = await confit({ basedir }).addDefault('override.json').create();

expect((config.get() as ReturnType<typeof JSON.parse>).child.grandchild.secret).toBe('santa');
expect((config.get() as ReturnType<typeof JSON.parse>).child.grandchild.name).toBe('grandchild');
expect((config.get() as ReturnType<typeof JSON.parse>).child.grandchild.name).toBe(
'grandchild',
);
expect((config.get() as ReturnType<typeof JSON.parse>).child.grandchild.another).toBe('claus');
});

Expand Down Expand Up @@ -345,14 +384,35 @@ describe('confit', () => {
ignoreme: 'file:./path/to/mindyourbusiness',
});
const basedir = path.join(__dirname, '..', '__tests__', 'defaults');
const config = await confit<{
fromlocal: string;
ignoreme?: string;
} & BaseConfitSchema>({
const config = await confit<
{
fromlocal: string;
ignoreme?: string;
} & BaseConfitSchema
>({
basedir,
excludeEnvVariables: ['ignoreme'],
}).create();
expect(config.get().fromlocal).toBe(env.local);
expect(config.get().ignoreme).toBeUndefined();
});

test('env via config', async () => {
const env = (process.env = {
SAMPLE: '8000',
sample: 'config:SAMPLE',
sampleNum: 'config:SAMPLE|d',
});
const basedir = path.join(__dirname, '..', '__tests__', 'defaults');
const config = await confit<
{
sample: string;
sampleNum: number;
} & BaseConfitSchema
>({
basedir,
}).create();
expect(config.get().sample).toBe(env.SAMPLE);
expect(config.get().sampleNum).toBe(Number(env.SAMPLE));
})
});
24 changes: 24 additions & 0 deletions src/shortstop/textHandlers.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { describe, test, expect } from 'vitest';

import { base64Handler, bufferHandler, unsetHandler } from '.';

describe('textHandlers', () => {
test('base64', () => {
const handler = base64Handler();
expect(handler('aGVsbG8gd29ybGQ=')).toBeInstanceOf(Buffer);
expect(handler('aGVsbG8gd29ybGQ=')).toEqual(Buffer.from('hello world'));
expect(handler('aGVsbG8gd29ybGQ=|utf8')).toBe('hello world');
});

test('base64url', () => {
const handler = bufferHandler('base64url');
expect(handler('aGVsbG8gd29ybGQ')).toBeInstanceOf(Buffer);
expect(handler('aGVsbG8gd29ybGQ')).toEqual(Buffer.from('hello world'));
expect(handler('aGVsbG8gd29ybGQ|utf8')).toBe('hello world');
});

test('unset', () => {
const handler = unsetHandler();
expect(handler()).toBeUndefined();
});
});
Loading

0 comments on commit 9ed5ae1

Please sign in to comment.